Compare commits
11 commits
0d69847a99
...
4f773c0d2f
Author | SHA1 | Date | |
---|---|---|---|
4f773c0d2f | |||
d386e0a520 | |||
9436218561 | |||
aa8812b837 | |||
a9c368e980 | |||
bad2f8fa18 | |||
e7eb852c66 | |||
21c8c34d7d | |||
e581cda081 | |||
c5ff4af87c | |||
cb7297eb88 |
6 changed files with 309 additions and 11 deletions
12
docker/docker-compose.yml
Normal file
12
docker/docker-compose.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
version: "3.6"
|
||||||
|
|
||||||
|
services:
|
||||||
|
zer.ooo:
|
||||||
|
image: d.xr.to/php
|
||||||
|
container_name: zer.ooo-test
|
||||||
|
volumes:
|
||||||
|
- ../:/sites/zer.ooo
|
||||||
|
working_dir: /sites/zer.ooo
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:80:80
|
||||||
|
command: ./docker/entrypoint.sh
|
10
docker/entrypoint.sh
Executable file
10
docker/entrypoint.sh
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo 'extension=iconv.so' >> /etc/php/php.ini
|
||||||
|
echo 'extension=sqlite3.so' >> /etc/php/php.ini
|
||||||
|
|
||||||
|
test -d vendor || ./bin/setup_web
|
||||||
|
test -d storage/ca || mkdir storage/ca
|
||||||
|
test -f storage/ca/ca.crt || ./bin/setup
|
||||||
|
|
||||||
|
php -S 0.0.0.0:80 -t public public/index.php
|
169
public/js/pages/configBuilder.js
Normal file
169
public/js/pages/configBuilder.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
$(function () {
|
||||||
|
var keyLocation = $('#key-location');
|
||||||
|
var keyLocationContainer = keyLocation.parent();
|
||||||
|
var decryptKeyCheckbox = $('#decrypt-key');
|
||||||
|
var decryptKeyCheckboxContainer = decryptKeyCheckbox.parent();
|
||||||
|
var password = $('#password');
|
||||||
|
var passwordContainer = password.parent();
|
||||||
|
var embedConfiguration = $('#want-embedded');
|
||||||
|
var embedConfigurationContainer = embedConfiguration.parent();
|
||||||
|
var keyFileContent = null;
|
||||||
|
var buildConfigZip = $('#build-config-zip');
|
||||||
|
var getCertificateForm = $('#get-certificate-form');
|
||||||
|
|
||||||
|
decryptKeyCheckbox.change(function () {
|
||||||
|
if (this.checked) {
|
||||||
|
passwordContainer.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordContainer.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleKeyFile(element) {
|
||||||
|
keyFileContent = element.target.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve zip file with post
|
||||||
|
getCertificateForm.submit(function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleZipResult(data) {
|
||||||
|
var blobUrl = URL.createObjectURL(data);
|
||||||
|
saveBlobUrl(blobUrl, 'config.zip');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEmbeddedResult(data) {
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
|
||||||
|
fileReader.onload = function() {
|
||||||
|
var text = this.result;
|
||||||
|
if (keyFileContent === null) {
|
||||||
|
saveText(text, 'server-embedded.conf');
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = /<key>/.exec(text);
|
||||||
|
matchOffset = match.index + 6;
|
||||||
|
text = text.substring(0, matchOffset) + keyFileContent + text.substring(matchOffset);
|
||||||
|
|
||||||
|
if (decryptKeyCheckbox.prop('checked') === true) {
|
||||||
|
var keyPassword = password.val();
|
||||||
|
text = decryptKey(text, keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveText(text, 'server-embedded.conf');
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.readAsText(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptKey(text, keyPassword) {
|
||||||
|
var match = /<key>/.exec(text);
|
||||||
|
|
||||||
|
var keyStartOffset = match.index + 6;
|
||||||
|
|
||||||
|
match = /<\/key>/.exec(text);
|
||||||
|
|
||||||
|
var keyEndOffset = match.index;
|
||||||
|
|
||||||
|
var keyContent = text.substring(keyStartOffset, keyEndOffset);
|
||||||
|
|
||||||
|
var decryptedPrivateKey = forge.pki.decryptRsaPrivateKey(keyContent.trim(), keyPassword);
|
||||||
|
var decryptedPem = forge.pki.privateKeyToPem(decryptedPrivateKey);
|
||||||
|
|
||||||
|
return text.substring(0, keyStartOffset) + decryptedPem + text.substring(keyEndOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveText(text, fileName) {
|
||||||
|
var blob = new Blob([text], {type: 'text/plain'});
|
||||||
|
var blobUrl = URL.createObjectURL(blob);
|
||||||
|
saveBlobUrl(blobUrl, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadElement = document.createElement("a");
|
||||||
|
document.body.appendChild(downloadElement);
|
||||||
|
downloadElement.style = "display: none";
|
||||||
|
|
||||||
|
function saveBlobUrl(url, fileName) {
|
||||||
|
downloadElement.href = url;
|
||||||
|
downloadElement.download = fileName;
|
||||||
|
downloadElement.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitCertificateForm(event) {
|
||||||
|
var url = getCertificateForm.attr('action'),
|
||||||
|
method = getCertificateForm.attr('method'),
|
||||||
|
data = getCertificateForm.serialize(),
|
||||||
|
handler = handleZipResult;
|
||||||
|
|
||||||
|
if (embedConfiguration.prop('checked') === true) {
|
||||||
|
handler = handleEmbeddedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
//this.response is what you're looking for
|
||||||
|
handler(this.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open(method, url);
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
xhr.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildConfigZip.on('click', submitCertificateForm);
|
||||||
|
|
||||||
|
keyLocationContainer.on('show', function () {
|
||||||
|
decryptKeyCheckboxContainer.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
keyLocationContainer.on('hide', function () {
|
||||||
|
decryptKeyCheckboxContainer.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
decryptKeyCheckboxContainer.on('show', function () {
|
||||||
|
if (decryptKeyCheckbox.prop('checked') === true) {
|
||||||
|
passwordContainer.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
decryptKeyCheckboxContainer.on('hide', function () {
|
||||||
|
passwordContainer.hide();
|
||||||
|
passwordContainer.val('');
|
||||||
|
});
|
||||||
|
|
||||||
|
function setFileSelectVisibility() {
|
||||||
|
var selectedOption = $('#certificate option:selected');
|
||||||
|
|
||||||
|
if (selectedOption.data('hasPrivateKey') != '1') {
|
||||||
|
keyLocationContainer.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLocationContainer.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLocation.change(function () {
|
||||||
|
var file = this.files[0];
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = handleKeyFile;
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
embedConfiguration.change(function () {
|
||||||
|
if (this.checked) {
|
||||||
|
setFileSelectVisibility();
|
||||||
|
decryptKeyCheckboxContainer.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptKeyCheckboxContainer.hide();
|
||||||
|
keyLocationContainer.hide();
|
||||||
|
});
|
||||||
|
});
|
|
@ -16,20 +16,38 @@ class Action extends Session
|
||||||
$zipFile = tempnam(sys_get_temp_dir(), '0zip');
|
$zipFile = tempnam(sys_get_temp_dir(), '0zip');
|
||||||
$zip = new \ZipArchive();
|
$zip = new \ZipArchive();
|
||||||
$zip->open($zipFile, \ZipArchive::CREATE);
|
$zip->open($zipFile, \ZipArchive::CREATE);
|
||||||
|
$cert = $this->post('cert');
|
||||||
|
|
||||||
|
if ($cert === null) {
|
||||||
|
$this->getResponse()
|
||||||
|
->withStatus(500)
|
||||||
|
->write('Stop messing with the form');
|
||||||
|
}
|
||||||
|
|
||||||
|
$wantEmbedded = $this->post('want-embedded');
|
||||||
|
|
||||||
$server = ServerQuery::create()->findOneByFingerprint($this->post('fingerprint'));
|
$server = ServerQuery::create()->findOneByFingerprint($this->post('fingerprint'));
|
||||||
$name = $server->getFqdn();
|
$name = $server->getFqdn();
|
||||||
|
|
||||||
$this->fillZipWithCaAndConfig($zip, $server);
|
if ($wantEmbedded !== null) {
|
||||||
|
|
||||||
$cert = $this->post('cert');
|
|
||||||
|
|
||||||
if ($cert !== null) {
|
|
||||||
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
|
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
|
||||||
$this->addClientCertificateData($zip, $certModel);
|
$config = $this->getEmbeddedConfig($server, $certModel);
|
||||||
|
|
||||||
$name .= '-' . $certModel->getName() . '.' . $certModel->getSerial();
|
$name .= '-' . $certModel->getName() . '.' . $certModel->getSerial();
|
||||||
|
|
||||||
|
return $this->getResponse()
|
||||||
|
->withHeader('Content-Type', 'text/plain')
|
||||||
|
->withHeader('Content-Disposition', 'attachment; filename="' . $name . '-embedded.conf"')
|
||||||
|
->write($config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
|
||||||
|
$this->addClientCertificateData($zip, $certModel);
|
||||||
|
|
||||||
|
$name .= '-' . $certModel->getName() . '.' . $certModel->getSerial();
|
||||||
|
|
||||||
|
$this->fillZipWithCaAndConfig($zip, $server);
|
||||||
|
|
||||||
$zip->close();
|
$zip->close();
|
||||||
|
|
||||||
$zipContents = file_get_contents($zipFile);
|
$zipContents = file_get_contents($zipFile);
|
||||||
|
@ -52,6 +70,33 @@ class Action extends Session
|
||||||
$zip->addFromString('ca.crt', file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt'));
|
$zip->addFromString('ca.crt', file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Server $server
|
||||||
|
* @param Certificate $cert
|
||||||
|
* @throws \Twig_Error_Loader
|
||||||
|
* @throws \Twig_Error_Runtime
|
||||||
|
* @throws \Twig_Error_Syntax
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEmbeddedConfig($server, $cert)
|
||||||
|
{
|
||||||
|
/** @var \Twig_Environment $twig */
|
||||||
|
$twig = $this->get('twig');
|
||||||
|
|
||||||
|
$parameters = [
|
||||||
|
'server' => $server,
|
||||||
|
'ca' => file_get_contents($this->getCore()->getBaseDir() . '/storage/ca/ca.crt'),
|
||||||
|
'cert' => $cert->getCertificate(),
|
||||||
|
'key' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($cert->hasPrivateKey()) {
|
||||||
|
$parameters['key'] = $cert->getPrivateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $twig->render('etc/openvpn-client-embedded.conf.twig', $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Server $server
|
* @param Server $server
|
||||||
* @return string
|
* @return string
|
||||||
|
|
34
views/etc/openvpn-client-embedded.conf.twig
Normal file
34
views/etc/openvpn-client-embedded.conf.twig
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
remote-cert-tls server
|
||||||
|
|
||||||
|
cipher AES-256-CBC
|
||||||
|
|
||||||
|
comp-lzo
|
||||||
|
|
||||||
|
<ca>
|
||||||
|
{{ ca|trim }}
|
||||||
|
</ca>
|
||||||
|
|
||||||
|
<cert>
|
||||||
|
{{ cert|trim }}
|
||||||
|
</cert>
|
||||||
|
|
||||||
|
<key>
|
||||||
|
{{ key|trim }}
|
||||||
|
</key>
|
|
@ -1,8 +1,19 @@
|
||||||
{% extends "panel.html.twig" %}
|
{% extends "panel.html.twig" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
<script src="/js/jquery.min.js"></script>
|
||||||
|
<script src="/js/forge.min.js"></script>
|
||||||
|
<script src="/js/jszip.min.js"></script>
|
||||||
|
<script src="/js/FileSaver.min.js"></script>
|
||||||
|
<script src="/js/pages/configBuilder.js"></script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel_contents %}
|
{% block panel_contents %}
|
||||||
<h1 class="title">Config builder</h1>
|
<h1 class="title">Config builder</h1>
|
||||||
<form target="_blank" action="/panel/config-builder" method="post" class="form">
|
<form target="_blank" action="/panel/config-builder" method="post" class="form" id="get-certificate-form">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="server">Server</label>
|
<label for="server">Server</label>
|
||||||
<select name="fingerprint" id="server">
|
<select name="fingerprint" id="server">
|
||||||
|
@ -15,12 +26,29 @@
|
||||||
<label for="certificate">Certificate</label>
|
<label for="certificate">Certificate</label>
|
||||||
<select name="cert" id="certificate">
|
<select name="cert" id="certificate">
|
||||||
{% for certificate in user.getActiveCertificates() %}
|
{% for certificate in user.getActiveCertificates() %}
|
||||||
<option value="{{ certificate.getName() }}">{{ certificate.getName() }}</option>
|
<option value="{{ certificate.getName() }}" data-has-private-key="{{ certificate.hasPrivateKey() ? '1' : '0' }}">{{ certificate.getName() }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="row">
|
||||||
<button type="submit">Build</button>
|
<label for="want-embedded">Generate embedded server config</label>
|
||||||
|
<input type="checkbox" id="want-embedded" name="want-embedded">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="row" style="display: none">
|
||||||
|
<label for="key-location">Select key file</label>
|
||||||
|
<input type="file" id="key-location" name="key-location">
|
||||||
|
</div>
|
||||||
|
<div class="row" style="display: none">
|
||||||
|
<label for="decrypt-key">Decrypt embedded private key?</label>
|
||||||
|
<input type="checkbox" id="decrypt-key" name="decrypt-key">
|
||||||
|
</div>
|
||||||
|
<div class="row" style="display: none">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password">
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<button id="build-config-zip" type="submit">Build</button>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue