diff --git a/Vagrantfile b/Vagrantfile index 404c80e..6d57e6b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,7 +8,7 @@ Vagrant.configure(2) do |config| web.vm.provision :shell, inline: </dev/null apt-get -qq update 2>/dev/null - apt-get -qq install php7.0-cli php7.0-sqlite3 2>/dev/null >/dev/null; + apt-get -qq install php7.0-cli php7.0-sqlite3 php7.0-zip 2>/dev/null >/dev/null; start-stop-daemon -bS --quiet --make-pidfile --pidfile /var/run/zerooo.pid --startas /bin/bash -- -c "exec php -S 0:8888 -t /vagrant/public/ /vagrant/public/index.php > /var/log/zerooo.log 2>&1"; installphp web.vm.network "forwarded_port", guest: 8888, host: 8888 @@ -18,6 +18,10 @@ installphp vpn.vm.box = "ubuntu/wily64" vpn.vm.network "private_network", ip: "192.168.50.8" vpn.vm.synced_folder "../zer.ooo-server", "/server" + vpn.vm.network "forwarded_port", guest: 7864, host: 7864 + vpn.vm.provision :shell, inline: < $STORAGE/serial; -echo 01 > $STORAGE/crl_serial; -touch $STORAGE/database; -touch $STORAGE/database.attr; +echo 01 > "${STORAGE}/serial"; +echo 01 > "${STORAGE}/crl_serial"; +touch "${STORAGE}/database"; +touch "${STORAGE}/database.attr"; -$BASEDIR/bin/create-ca $1; -$BASEDIR/bin/create-crl; +"${BASEDIR}/bin/create-ca" $1; +"${BASEDIR}/bin/create-crl"; \ No newline at end of file diff --git a/config/routes.yml b/config/routes.yml index f5faa52..683ef54 100644 --- a/config/routes.yml +++ b/config/routes.yml @@ -1,6 +1,9 @@ prefix: Eater\Glim\Handler\ routes: /: Home + /install: + get: Install\Show + post: Install\Action /login: get: Login\Show post: Login\Action @@ -19,11 +22,15 @@ routes: /revoke: post: Panel\Certificates\Revoke /server: + /remove: + post: Panel\Servers\Remove /sign: post: Panel\Servers\Sign /{fingerprint}: get: Panel\Servers\Edit\Show post: Panel\Servers\Edit\Action + /{fingerprint}/config: Panel\Servers\Config + /{fingerprint}/config/{cert}: Panel\Servers\Config /server: /register: post: Server\Register \ No newline at end of file diff --git a/etc/openssl.conf b/etc/openssl.conf index 4d8191d..66656a9 100644 --- a/etc/openssl.conf +++ b/etc/openssl.conf @@ -31,7 +31,7 @@ subjectKeyIdentifier=hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = serverAuth keyUsage = digitalSignature,keyEncipherment -crlDistributionPoints = URI:http://0b.ae/crl +crlDistributionPoints = URI:http://localhost:8888/crl [client_ext] subjectKeyIdentifier=hash basicConstraints = CA:FALSE diff --git a/public/js/edit_server.js b/public/js/edit_server.js index 25cf0cf..f8f3ece 100644 --- a/public/js/edit_server.js +++ b/public/js/edit_server.js @@ -14,15 +14,17 @@ $(function () { function save(callback) { var fingerprint = $('.server-form').data('fingerprint'); - var data = $('.server-form input').serializeArray(); + var data = $('.server-form input,.server-form select').serializeArray(); $.post('/panel/server/' + fingerprint, data, function (data) { + console.log(data); if (!data.success) { $('h2').after(error.text(data.error)); + return; } callback(); - }); + }, 'json'); } function sign() { @@ -35,8 +37,12 @@ $(function () { function (data) { if (!data.success) { $('h2').after(error.text(data.error)); + return; } - } + + location.reload(); + }, + 'json' ); } }); \ No newline at end of file diff --git a/public/js/new_certificate.js b/public/js/new_certificate.js index efc3f9c..548e635 100644 --- a/public/js/new_certificate.js +++ b/public/js/new_certificate.js @@ -9,8 +9,8 @@ $(function () { return; } - if (!/^[A-Za-z0-9_-]+$/.test(name)) { - $('h2').after(error.text("Only alphanumeric, _ and - allowed in name")); + if (!/^[A-Za-z0-9\-]+$/.test(name)) { + $('h2').after(error.text("Only alphanumeric and - allowed in name")); return; } @@ -57,7 +57,7 @@ $(function () { } else { $('h2').after(error.text(data.error)); } - }); + }, 'json'); }); $("#wantsPassword").change(function () { diff --git a/public/js/panel.js b/public/js/panel.js index 384d024..d6ea4af 100644 --- a/public/js/panel.js +++ b/public/js/panel.js @@ -26,4 +26,11 @@ $(function(){ $('.revoke-password').val(''); }); + + $('.remove').click(function () { + var that = this; + $.post('/panel/server/remove',{ fingerprint: $(this).data('fingerprint') }, function () { + $(that).parents('tr').first().remove(); + }); + }); }); \ No newline at end of file diff --git a/src/Core.php b/src/Core.php index eec0fa0..179ecf7 100644 --- a/src/Core.php +++ b/src/Core.php @@ -324,6 +324,7 @@ class Core implements ContainerInterface public function handle($class, Request $request, Response $response, ContainerInterface $containerInterface) { $this->startTimer(['response']); + /** @var Handler\Main $handler */ $handler = new $class($this, $request, $response, $containerInterface); $this->startTimer(['response/before-handle']); diff --git a/src/Handler/CA.php b/src/Handler/CA.php index 08ebf71..28c96a0 100644 --- a/src/Handler/CA.php +++ b/src/Handler/CA.php @@ -2,11 +2,11 @@ namespace Eater\Glim\Handler; -class CA extends Session +class CA extends Main { function handle() { - return$this->getResponse() + return $this->getResponse() ->withHeader('Content-Type', 'plain/text') ->withHeader('Content-Disposition', 'attachment; filename="ca.crt"') ->write(file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt')); diff --git a/src/Handler/CRL.php b/src/Handler/CRL.php new file mode 100644 index 0000000..c02582b --- /dev/null +++ b/src/Handler/CRL.php @@ -0,0 +1,21 @@ +getResponse() + ->withHeader('Content-Type', 'plain/text') + ->withHeader('Content-Disposition', 'attachment; filename="crl.pem"') + ->write(file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/crl.pem')); + } +} \ No newline at end of file diff --git a/src/Handler/Install/Action.php b/src/Handler/Install/Action.php new file mode 100644 index 0000000..a8386e0 --- /dev/null +++ b/src/Handler/Install/Action.php @@ -0,0 +1,53 @@ +get('user'); + + $caCN = $this->post('ca-cn'); + + /* @var \Aura\Session\Session $session */ + $session = $this->get('session'); + $segment = $session->getSegment('main'); + + if (trim($caCN) === "") { + $segment->setFlash('error', 'CA Common name can\'t be empty'); + return $this->redirect('/install'); + } + + /** @var \Twig_Environment $twig */ + $twig = $this->get('twig'); + + $opensslConf = $twig->render('etc/openssl-ca.conf.twig', [ + 'host' => $this->post('domainWithPort') + ]); + + file_put_contents($this->getCore()->getBaseDir() . '/etc/openssl.conf', $opensslConf); + + exec($this->getCore()->getBaseDir() . '/bin/clean-all'); + exec($this->getCore()->getBaseDir() . '/bin/setup ' . escapeshellarg($caCN)); + + $newUser = null; + + try { + $newUser = $user->createSuperuser($this->post('username'), $this->post('password')); + } catch (\Exception $e) { + $segment->setFlash("error", $e->getMessage()); + } + + if ($newUser === null) { + return $this->redirect('/install'); + } + + $segment->set('user', $newUser); + + return $this->redirect('/panel'); + } +} \ No newline at end of file diff --git a/src/Handler/Install/Show.php b/src/Handler/Install/Show.php new file mode 100644 index 0000000..1e98f52 --- /dev/null +++ b/src/Handler/Install/Show.php @@ -0,0 +1,54 @@ +findOne()) { + return $this->redirect('/login'); + } + } + + public function handle() + { + $return = 1; + $execEnabled = $this->isExecEnabled(); + $hasOpenSsl = false; + if ($execEnabled) { + exec('command -v openssl', $output, $return); + $hasOpenSsl = $return === 0; + } + + $data = [ + 'hasExecEnabled' => $execEnabled, + 'hasOpenSsl' => $hasOpenSsl, + 'hasOpenSslExtension' => extension_loaded('openssl'), + 'hasZipExtension' => extension_loaded('zip'), + 'hostname' => parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST), + 'hostnameWithPort' => $_SERVER['HTTP_HOST'] + ]; + + return $this->render('install.html.twig', $data); + } + + private function isExecEnabled() { + if (ini_get('safe_mode')) { + return false; + } else { + $d = ini_get('disable_functions'); + $s = ini_get('suhosin.executor.func.blacklist'); + if ("$d$s") { + $array = preg_split('/,\s*/', "$d,$s"); + if (in_array('exec', $array)) { + return false; + } + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/Handler/Login/Show.php b/src/Handler/Login/Show.php index 8629c5e..a4f1e66 100644 --- a/src/Handler/Login/Show.php +++ b/src/Handler/Login/Show.php @@ -11,9 +11,25 @@ namespace Eater\Glim\Handler\Login; use Aura\Session\Segment; use Eater\Glim\Handler\Main; +use Eater\Glim\Model\UserQuery; class Show extends Main { + public function beforeHandle() + { + $session = $this->get('session'); + $segment = $session->getSegment('main'); + $user = $segment->get('user'); + + if ($user) { + $this->redirect('/panel'); + } + + if (!UserQuery::create()->findOne()) { + return $this->redirect('/install'); + } + } + public function handle() { /** @var Segment $segment */ diff --git a/src/Handler/Main.php b/src/Handler/Main.php index 3fa2c41..d09c167 100644 --- a/src/Handler/Main.php +++ b/src/Handler/Main.php @@ -146,7 +146,6 @@ abstract class Main implements ContainerInterface return $this->getResponse() ->withHeader('Content-Type', 'application/json') ->write(json_encode($array)); - } /** diff --git a/src/Handler/Panel/Certificates/Download.php b/src/Handler/Panel/Certificates/Download.php index fc634b3..75b9c5a 100644 --- a/src/Handler/Panel/Certificates/Download.php +++ b/src/Handler/Panel/Certificates/Download.php @@ -29,7 +29,7 @@ class Download extends Session return $this->getResponse() ->withHeader('Content-Type', 'plain/text') - ->withHeader('Content-Disposition', 'attachment; filename="' . $name . '.' . $this->getUser()->getUsername() .'.crt"') + ->withHeader('Content-Disposition', 'attachment; filename="' . $name . '.' . $cert->getSerial() .'.crt"') ->write($cert->getCertificate()); } } \ No newline at end of file diff --git a/src/Handler/Panel/Servers/Config.php b/src/Handler/Panel/Servers/Config.php new file mode 100644 index 0000000..847e7e4 --- /dev/null +++ b/src/Handler/Panel/Servers/Config.php @@ -0,0 +1,89 @@ +open($zipFile, \ZipArchive::CREATE); + $server = ServerQuery::create()->findOneByFingerprint($this->attr('fingerprint')); + $name = $server->getFqdn(); + + $this->fillZipWithCaAndConfig($zip, $server); + + $cert = $this->attr('cert'); + + if ($cert !== null) { + $certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert); + $this->addClientCertificateData($zip, $certModel); + + $name .= '-' . $certModel->getName() . '.' . $certModel->getSerial(); + } + + $zip->close(); + + $zipContents = file_get_contents($zipFile); + unlink($zipFile); + + return $this->getResponse() + ->withHeader('Content-Type', 'application/zip') + ->withHeader('Content-Disposition', 'attachment; filename="' . $name . '.zip"') + ->write($zipContents); + } + + /** + * @param \ZipArchive $zip + * @param Server $server + */ + public function fillZipWithCaAndConfig($zip, $server) + { + $config = $this->getConfigForServerFingerprint($server); + $zip->addFromString('server.conf', $config); + $zip->addFromString('ca.crt', file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt')); + } + + /** + * @param Server $server + * @return string + */ + public function getConfigForServerFingerprint($server) + { + /** @var \Twig_Environment $twig */ + $twig = $this->get('twig'); + + $config = $twig->render('etc/openvpn-client.conf.twig', [ + 'server' => $server + ]); + + return $config; + } + + /** + * @param \ZipArchive $zip + * @param Certificate $cert + */ + public function addClientCertificateData($zip, $cert) + { + $zip->addFromString('client.crt', $cert->getCertificate()); + + if ($cert->hasPrivateKey()) { + $zip->addFromString('client.key', $cert->getPrivateKey()); + } + } +} \ No newline at end of file diff --git a/src/Handler/Panel/Servers/Edit/Action.php b/src/Handler/Panel/Servers/Edit/Action.php index deb1ae3..59f9774 100644 --- a/src/Handler/Panel/Servers/Edit/Action.php +++ b/src/Handler/Panel/Servers/Edit/Action.php @@ -4,6 +4,7 @@ namespace Eater\Glim\Handler\Panel\Servers\Edit; use Eater\Glim\Handler\Session; use Eater\Glim\Model\ServerQuery; +use Eater\Glim\Service\Server; class Action extends Session { @@ -23,13 +24,24 @@ class Action extends Session # Config $server->setInternalIp($this->post('internal-ip')); $server->setNetmask($this->post('netmask')); - $server->setPort($this->post('post')); + $server->setPort($this->post('port')); $server->setProtocol($this->post('protocol')); $server->setFirstDns($this->post('first-dns')); $server->setSecondDns($this->post('second-dns')); $server->save(); - $this->json([ 'success'=> true ]); + /** @var Server $serverService */ + $serverService = $this->get('server'); + try { + $serverService->deliverOpenVPNConfig($server); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } + + return $this->json([ 'success'=> true ]); } } \ No newline at end of file diff --git a/src/Handler/Panel/Servers/Remove.php b/src/Handler/Panel/Servers/Remove.php new file mode 100644 index 0000000..1598aa7 --- /dev/null +++ b/src/Handler/Panel/Servers/Remove.php @@ -0,0 +1,24 @@ +findOneByFingerprint($this->post('fingerprint')); + $server->delete(); + + return $this->json([ + 'success' => true + ]); + } +} \ No newline at end of file diff --git a/src/Handler/Panel/Servers/Sign.php b/src/Handler/Panel/Servers/Sign.php index 1279a5b..d626f88 100644 --- a/src/Handler/Panel/Servers/Sign.php +++ b/src/Handler/Panel/Servers/Sign.php @@ -17,10 +17,12 @@ class Sign extends Session /** @var CA $ca */ $ca = $this->get('ca'); $server = ServerQuery::create()->findOneByFingerprint($this->post('fingerprint')); - $publicKey = $server->getPublicKey(); + /** @var Server $serverManager */ + $serverManager = $this->get('server'); try { - $crt = $ca->signServerKey($publicKey, $server->getFqdn()); + $csr = $serverManager->getCsrFromServer($server); + $crt = $ca->signServerKey($csr, $server->getFqdn()); } catch (\Exception $e) { return $this->json([ 'success' => false, @@ -31,9 +33,6 @@ class Sign extends Session $server->setCertificate($crt); $server->save(); - /** @var Server $serverManager */ - $serverManager = $this->get('server'); - try { $serverManager->deliverSignedCertificate($server); } catch (\Exception $e) { @@ -43,7 +42,7 @@ class Sign extends Session ]); } - return $this->json([ + return $this->json([ 'success' => true ]); } diff --git a/src/Handler/Session.php b/src/Handler/Session.php index 54098bb..4d01348 100644 --- a/src/Handler/Session.php +++ b/src/Handler/Session.php @@ -9,6 +9,7 @@ namespace Eater\Glim\Handler; use Aura\Session\Segment; +use Eater\Glim\Model\Base\UserQuery; use Eater\Glim\Model\User; use Eater\Glim\Service\TwigVars; @@ -48,6 +49,10 @@ class Session extends Main public function beforeHandle() { + if (!UserQuery::create()->findOne()) { + return $this->redirect('/install'); + } + /* @var \Aura\Session\Session */ $session = $this->get('session'); /** @var TwigVars $twigVar */ diff --git a/src/Model/Certificate.php b/src/Model/Certificate.php index cefa73f..6932c46 100644 --- a/src/Model/Certificate.php +++ b/src/Model/Certificate.php @@ -16,5 +16,12 @@ use Eater\Glim\Model\Base\Certificate as BaseCertificate; */ class Certificate extends BaseCertificate { - + /** + * @return bool + */ + public function hasPrivateKey() + { + $privateKey = $this->getPrivateKey(); + return !empty($privateKey); + } } diff --git a/src/Model/Server.php b/src/Model/Server.php index 1887420..7a9765a 100644 --- a/src/Model/Server.php +++ b/src/Model/Server.php @@ -16,5 +16,8 @@ use Eater\Glim\Model\Base\Server as BaseServer; */ class Server extends BaseServer { - + public function getNetmaskIp() + { + return long2ip(-1 << (32 - (int)$this->getNetmask())); + } } diff --git a/src/Model/User.php b/src/Model/User.php index 803a9b6..0c3cbf5 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -16,5 +16,4 @@ use Eater\Glim\Model\Base\User as BaseUser; */ class User extends BaseUser { - } diff --git a/src/Service/CA.php b/src/Service/CA.php index 9febbf3..af2f3e0 100644 --- a/src/Service/CA.php +++ b/src/Service/CA.php @@ -13,6 +13,14 @@ use Eater\Glim\Core; class CA extends Main { + public function getPrivateKey() + { + /** @var Core $core */ + $core = $this->get('core'); + + return file_get_contents($core->getBaseDir() . '/storage/ca/ca.key'); + } + /** * @return string */ @@ -56,47 +64,14 @@ class CA extends Main return $crt; } - /** - * @param string $publicKey - * @param string $fqdn - * @return string - * @throws \Exception - */ - public function createCSRForKeyAndFqdn($publicKey, $fqdn) - { - /** @var Core $core */ - $core = $this->get('core'); - - $csrPath = tempnam(sys_get_temp_dir(), '0.'); - $pubPath = tempnam(sys_get_temp_dir(), '0.'); - - file_put_contents($pubPath, $publicKey); - - exec(escapeshellcmd($core->getBaseDir() . '/bin/create-csr') . ' ' . escapeshellarg($fqdn) . ' ' . escapeshellarg($csrPath) . ' ' . escapeshellarg($pubPath) . ' 2>&1', $output, $exitCode); - - if ($exitCode !== 0) { - throw new \Exception("Failed creating CSR: " . implode("\n", $output)); - } - - $csr = file_get_contents($csrPath); - - unlink($pubPath); - unlink($csrPath); - - return $csr; - } - /** * Signs a client certificate and returns the signed certificate - * @param string $publicKey - * @param string $fqdn + * @param string $csr * @return string * @throws \Exception */ - public function signServerKey($publicKey, $fqdn) + public function signServerKey($csr) { - $csr = $this->createCSRForKeyAndFqdn($publicKey, $fqdn); - /** @var Core $core */ $core = $this->get('core'); @@ -197,13 +172,10 @@ class CA extends Main } public function signWithCA($data) { - /** @var Core $core */ - $core = $this->get('core'); - - $privateKeyPlain = file_get_contents($core->getBaseDir() . '/storage/ca/ca.key'); + $privateKeyPlain = $this->getPrivateKey(); $privateKey = openssl_get_privatekey($privateKeyPlain); - $result = openssl_sign($data, $signature, $privateKey); + $result = openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256); if ($result) { return $signature; diff --git a/src/Service/Server.php b/src/Service/Server.php index e75c62a..910ca5a 100644 --- a/src/Service/Server.php +++ b/src/Service/Server.php @@ -5,6 +5,7 @@ namespace Eater\Glim\Service; use Eater\Glim\Model\Server as ServerModel; use GuzzleHttp\Client as HttpClient; use Eater\Glim\Core; +use Psr\Http\Message\ResponseInterface; class Server extends Main { @@ -20,18 +21,52 @@ class Server extends Main return $this->httpClient; } + public function getCsrFromServer(ServerModel $server) { + $response = $this->doSignedRequest($server, '/create-csr', [ + 'hostname' => $server->getFqdn() + ]); + + if ($response->getStatusCode() !== 200) { + throw new \Exception('Requesting CSR failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents()); + } + + $data = $this->decryptResponse($server, $response); + + return $data['csr']; + } + /** * @param ServerModel $server * @throws \Exception */ public function deliverSignedCertificate(ServerModel $server) { $response = $this->doSignedRequest($server, '/deliver-crt', [ - 'certficate' => $server->getCertificate() + 'certificate' => $server->getCertificate() ]); if ($response->getStatusCode() !== 200) { throw new \Exception('Delivering signed certificate failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents()); } + + $server->setStatus('signed'); + $server->save(); + } + + + public function deliverOpenVPNConfig(ServerModel $server) { + /** @var \Twig_Environment $twig */ + $twig = $this->get('twig'); + $config = $twig->render('etc/openvpn-server.conf.twig', [ + 'server' => $server + ]); + + $response = $this->doSignedRequest($server, '/update-openvpn-config', [ + 'config' => $config + ]); + + if ($response->getStatusCode() !== 200) { + throw new \Exception('Updating openvpn server config failed (' . $response->getStatusCode() . '): ' . $response->getBody()->getContents()); + } } /** @@ -51,27 +86,42 @@ class Server extends Main $json = json_encode($data); - $password = bin2hex(openssl_random_pseudo_bytes(32)); + $password = bin2hex(openssl_random_pseudo_bytes(48)); $pubKey = openssl_get_publickey($server->getPublicKey()); $success = openssl_public_encrypt($password, $crypted, $pubKey, OPENSSL_PKCS1_PADDING); if (!$success) { throw new \Exception('Encrypting data failed: ' . openssl_error_string() . openssl_error_string()); } + + $body = bin2hex($crypted) . bin2hex(openssl_encrypt($json, 'blowfish', substr($password, 0, 32), true, substr($password, 32, 8))); + return $client->post('http://' . $server->getExternalIp() . ':' . static::MANAGEMENT_PORT . $path, [ + 'body' => $body + ]); + } - $this->get('logger')->addDebug('Password: ' . $password); - - $body = [ - bin2hex($crypted), - bin2hex(openssl_encrypt($server->getCertificate(), 'aes-256-cbc', $password, 'help')) - ]; + public function decryptResponse(ServerModel $server, ResponseInterface $response) { + $body = $response->getBody()->getContents(); + $this->get('logger')->addDebug($body); + $encryptedPasswordAndIV = hex2bin(substr($body, 0, 512)); + $encryptedBody = hex2bin(substr($body, 512)); - $this->get('logger')->addDebug('Help: ' . var_export([$json, $body], true)); + /** @var CA $ca */ + $ca = $this->get('ca'); + $success = openssl_private_decrypt($encryptedPasswordAndIV, $passwordAndIV, $ca->getPrivateKey(), OPENSSL_PKCS1_PADDING); + if (!$success) { + throw new \Exception('Decrypting data failed: ' . openssl_error_string() . openssl_error_string()); + } - return $client->post('http://' . $server->getExternalIp() . ':' . static::MANAGEMENT_PORT . $path, [ - 'json' => $body - ]); + $json = openssl_decrypt($encryptedBody, 'blowfish', substr($passwordAndIV, 0, 32), true, substr($passwordAndIV, 32, 8)); + $data = \json_decode($json, true); + $ver = openssl_verify($server->getFingerprint(), hex2bin($data['signature']), $server->getPublicKey(), OPENSSL_ALGO_SHA256); + if ($ver === 1) { + return $data; + } else { + throw new \Exception("Checking signature failed ($ver): " . openssl_error_string()); + } } } diff --git a/src/Service/Twig.php b/src/Service/Twig.php index ba9d2cf..45ae29c 100644 --- a/src/Service/Twig.php +++ b/src/Service/Twig.php @@ -29,7 +29,8 @@ class Twig $loader = new \Twig_Loader_Filesystem($core->getBaseDir() . '/views/'); $twig = new \Twig_Environment($loader, array( 'cache' => $core->getBaseDir() . '/tmp/twig', - 'debug' => $debug + 'debug' => $debug, + 'displayErrorDetails' => $debug )); if ($twig) { diff --git a/src/Service/User.php b/src/Service/User.php index 0968739..6329ac2 100644 --- a/src/Service/User.php +++ b/src/Service/User.php @@ -24,17 +24,50 @@ class User extends Main throw new \Exception("Invalid invite code"); } + $this->validateUserParams($username, $password); + + $user = new UserModel(); + $user->setUsername($username); + $user->setPassword(\password_hash($password, PASSWORD_DEFAULT)); + $user->save(); + + $invite->delete(); + + return $user; + } + + public function validateUserParams($username, $password) { + if ($username === "") { + throw new \Exception("No username given"); + } + + if (!preg_match('~^[a-z0-9\-]+$~', $username)) { + throw new \Exception("Username can only consist of a-z, 0-9 and -"); + } + + if ($password === "") { + throw new \Exception("Password is nothing, though strong. we rather not have you use that"); + } + + if (strlen($password) < 9) { + throw new \Exception("Please pick a password with more then 8 characters"); + } + if ($this->exists($username)) { throw new \Exception("User already exists"); } + } + + public function createSuperuser($username, $password) { + + $this->validateUserParams($username, $password); $user = new UserModel(); $user->setUsername($username); $user->setPassword(\password_hash($password, PASSWORD_DEFAULT)); + $user->setSuperuser(true); $user->save(); - $invite->delete(); - return $user; } diff --git a/etc/openssl.conf.twig b/views/etc/openssl-ca.conf.twig similarity index 72% rename from etc/openssl.conf.twig rename to views/etc/openssl-ca.conf.twig index 8b7117e..0c49511 100644 --- a/etc/openssl.conf.twig +++ b/views/etc/openssl-ca.conf.twig @@ -6,15 +6,14 @@ default_ca=ca_default [v3_ca] [ca_default] crl_extensions=crl_ext -unique_subject=no -private_key=storage/ca.key -certificate=storage/ca.crt -new_certs_dir=storage/certs/ -database=storage/database +private_key=storage/ca/ca.key +certificate=storage/ca/ca.crt +new_certs_dir=storage/ca/certs/ +database=storage/ca/database default_md=sha256 policy=policy_only_commonname -serial=storage/serial -crlnumber=storage/crl_serial +serial=storage/ca/serial +crlnumber=storage/ca/crl_serial default_crl_days=1 [policy_only_commonname] countryName = optional @@ -32,14 +31,14 @@ subjectKeyIdentifier=hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = serverAuth keyUsage = digitalSignature,keyEncipherment -crlDistributionPoints = URI:http://{{ hostname }}/crl +crlDistributionPoints = URI:http://{{ host }}/crl [client_ext] subjectKeyIdentifier=hash basicConstraints = CA:FALSE -crlDistributionPoints = URI:http://{{ hostname }}/crl +crlDistributionPoints = URI:http://{{ host }}/crl [ca_ext] basicConstraints = CA:TRUE subjectKeyIdentifier=hash -crlDistributionPoints = URI:http://{{ hostname }}/crl +crlDistributionPoints = URI:http://{{ host }}/crl [crl_ext] authorityKeyIdentifier=keyid:always diff --git a/views/etc/openvpn-client.conf.twig b/views/etc/openvpn-client.conf.twig new file mode 100644 index 0000000..abc4c38 --- /dev/null +++ b/views/etc/openvpn-client.conf.twig @@ -0,0 +1,27 @@ +client + +dev zerooo +dev-type tun + +proto {{ server.getProtocol() }} + +remote {{ server.getExternalIp() }} {{ server.getPort() }} +resolv-retry infinite +nobind + +user nobody +group nogroup + +persist-key +persist-tun + +ca ca.crt +cert client.crt +key client.key + +remote-cert-tls server + +cipher BF-CBC + +comp-lzo +verb 3 \ No newline at end of file diff --git a/views/etc/openvpn-server.conf.twig b/views/etc/openvpn-server.conf.twig new file mode 100644 index 0000000..339880f --- /dev/null +++ b/views/etc/openvpn-server.conf.twig @@ -0,0 +1,32 @@ +port {{ server.getPort() }} +proto {{ server.getProtocol() }} + +dev ovpn0 +dev-type tun + +ca ca.crt +cert server.crt +key server.key + +dh dh2048.pem + +server {{ server.getInternalIp() }} {{ server.getNetmaskIp() }} + +ifconfig-pool-persist ipp.txt + +push "redirect-gateway def1 bypass-dhcp" + +push "dhcp-option DNS {{ server.getFirstDns() }}" +push "dhcp-option DNS {{ server.getSecondDns() }}" +keepalive 10 120 +cipher BF-CBC + +crl-verify crl.pem + +comp-lzo + +user nobody +group nogroup + +persist-key +persist-tun \ No newline at end of file diff --git a/views/install.html.twig b/views/install.html.twig new file mode 100644 index 0000000..295a979 --- /dev/null +++ b/views/install.html.twig @@ -0,0 +1,80 @@ +{% extends "base.html.twig" %} + +{% block content %} +
+
+
+
+
+

Welcome to your Zer.ooo install

+
+
+

Checking for extensions and settings

+
+
+ + + + + + + + + + + + + + + + + + + +
{{ hasExecEnabled ? 'Yes' : 'No' }}Is exec available?
{{ hasOpenSsl ? 'Yes' : 'No' }}Is the openssl binary available?
{{ hasOpenSslExtension ? 'Yes' : 'No' }}Is the openssl module loaded?
{{ hasZipExtension ? 'Yes' : 'No' }}Is the zip module loaded?
+
+ + {% if not (hasExecEnabled and hasOpenSslExtension and hasOpenSsl and hasZipExtension) %} +
+ Those functions are essential for the functionality of Zer.ooo, please enable them
+
+ Refresh +
+ {% else %} +
+

Create your superuser

+
+
+
+
+ + +
+
+ + +
+
+
+

Server details

+
+
+
+ + +
+
+ + +
+
+ +
+
+
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/views/panel.html.twig b/views/panel.html.twig index bbe7ed0..3551a58 100644 --- a/views/panel.html.twig +++ b/views/panel.html.twig @@ -8,7 +8,9 @@ {% block content %}
-

Certificates

+
+

Certificates

+
@@ -53,59 +55,22 @@
-

Servers

- - - - - - - - - - - - {% for server in servers %}\ - - - - - - - - {% else %} - - - - {% endfor %} - -
HostnameFingerprintLocationSpeedActions
- {{ server.getFqdn() }} - - {{ server.getFingerprint() }} - - {{ server.getLocation() }} - - {{ server.getSpeed() }} MB/s - -
- -
-
- There don't seem to be any servers yet :( -
- {% if user.getSuperuser() %} -

Registered servers

+
+

Servers

+
+
+ + - {% for server in registeredServers %} + {% for server in servers %} + + @@ -128,6 +119,46 @@ {% endfor %}
Hostname FingerprintLocationSpeed Actions
{{ server.getFqdn() }} @@ -113,9 +78,35 @@ {{ server.getFingerprint() }} + {{ server.getLocation() }} + + {{ server.getSpeed() }} MB/s +
- Details +
+ Download config + + +
+ {% if user.isSuperUser() %} + Edit + {% endif %}
+
+ {% if user.getSuperuser() %} +
+

Registered servers

+
+
+ + + + + + + + + + {% for server in registeredServers %} + + + + + + {% else %} + + + + {% endfor %} + +
HostnameFingerprintActions
+ {{ server.getFqdn() }} + + {{ server.getFingerprint() }} + +
+ + Edit +
+
+ There don't seem to be any servers yet :( +
+
{% endif %}