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');
|
||||
$zip = new \ZipArchive();
|
||||
$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'));
|
||||
$name = $server->getFqdn();
|
||||
|
||||
$this->fillZipWithCaAndConfig($zip, $server);
|
||||
|
||||
$cert = $this->post('cert');
|
||||
|
||||
if ($cert !== null) {
|
||||
if ($wantEmbedded !== null) {
|
||||
$certModel = CertificateQuery::create()->findOneByUserAndName($this->getUser(), $cert);
|
||||
$this->addClientCertificateData($zip, $certModel);
|
||||
|
||||
$config = $this->getEmbeddedConfig($server, $certModel);
|
||||
$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();
|
||||
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @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" %}
|
||||
|
||||
{% 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 %}
|
||||
<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">
|
||||
<label for="server">Server</label>
|
||||
<select name="fingerprint" id="server">
|
||||
|
@ -15,12 +26,29 @@
|
|||
<label for="certificate">Certificate</label>
|
||||
<select name="cert" id="certificate">
|
||||
{% 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 %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit">Build</button>
|
||||
<div class="row">
|
||||
<label for="want-embedded">Generate embedded server config</label>
|
||||
<input type="checkbox" id="want-embedded" name="want-embedded">
|
||||
</div>
|
||||
</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 %}
|
||||
|
|
Loading…
Reference in a new issue