vpn: refactor key handling code
Move JS code for listing, creating and deleting WG keys into a separate file and improve it somewhat. Also the related Python code.
This commit is contained in:
		
							parent
							
								
									8c9829b726
								
							
						
					
					
						commit
						1b26f0738a
					
				
					 3 changed files with 154 additions and 149 deletions
				
			
		
							
								
								
									
										114
									
								
								web/static/vpn.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								web/static/vpn.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					function delKey(key) {
 | 
				
			||||||
 | 
					    fetch('del', {
 | 
				
			||||||
 | 
					        credentials: 'include',
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        headers: { 'Content-Type': 'application/json' },
 | 
				
			||||||
 | 
					        body: JSON.stringify({ pubkey: key })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(response => {
 | 
				
			||||||
 | 
					        if (!response.ok)
 | 
				
			||||||
 | 
					            throw new Error('deleting key failed');
 | 
				
			||||||
 | 
					        return response.text();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(data => {
 | 
				
			||||||
 | 
					        fetchKeys();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => console.error(error));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function fetchKeys() {
 | 
				
			||||||
 | 
					    fetch('list', {
 | 
				
			||||||
 | 
					        credentials: 'include'
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(response => {
 | 
				
			||||||
 | 
					        if (!response.ok)
 | 
				
			||||||
 | 
					            throw new Error('fetching keys failed');
 | 
				
			||||||
 | 
					        return response.json();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(data => {
 | 
				
			||||||
 | 
					        const keys = document.querySelector('ul.keys');
 | 
				
			||||||
 | 
					        keys.innerHTML = '';
 | 
				
			||||||
 | 
					        const warning = document.querySelector('p#active-key-warning');
 | 
				
			||||||
 | 
					        warning.hidden = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let key of Object.values(data)) {
 | 
				
			||||||
 | 
					            var a = document.createElement('a');
 | 
				
			||||||
 | 
					            a.innerText = '✖';
 | 
				
			||||||
 | 
					            a.href = '';
 | 
				
			||||||
 | 
					            a.addEventListener('click', event => {
 | 
				
			||||||
 | 
					                delKey(key.key);
 | 
				
			||||||
 | 
					                event.preventDefault();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            var li = document.createElement('li');
 | 
				
			||||||
 | 
					            li.innerHTML = ' ' + (new Date(key.time*1000).toISOString().split('T')[0]) +
 | 
				
			||||||
 | 
					                ' <code>' + key.key + '</code> ' + key.name +
 | 
				
			||||||
 | 
					                (key.active ? '<font color="red"><sup>★</sup></font> ' : '');
 | 
				
			||||||
 | 
					            li.prepend(a);
 | 
				
			||||||
 | 
					            keys.appendChild(li);
 | 
				
			||||||
 | 
					            if (key.active)
 | 
				
			||||||
 | 
					                warning.hidden = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        document.querySelector('section.keys').style.display = (Object.keys(data).length ? 'unset' : 'none');
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => console.error(error));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.addEventListener('load', fetchKeys);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const form = document.querySelector('form#request');
 | 
				
			||||||
 | 
					form.addEventListener('submit', event => {
 | 
				
			||||||
 | 
					    event.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inputs = Array.from(document.querySelectorAll('form input'));
 | 
				
			||||||
 | 
					    const settings = document.querySelector('section#settings');
 | 
				
			||||||
 | 
					    const submit = document.querySelector('button#submit');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const key = wireguard.generateKeypair();
 | 
				
			||||||
 | 
					    const options = Object.fromEntries(inputs.map(e => [e.name, e.type === 'checkbox' ? e.checked : e.value]));
 | 
				
			||||||
 | 
					    const networks = Array.from(document.querySelectorAll('select#networks option:checked')).map(e => e.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submit.innerHTML = 'Obdelovanje…';
 | 
				
			||||||
 | 
					    submit.disabled = true;
 | 
				
			||||||
 | 
					    fetch('new', {
 | 
				
			||||||
 | 
					        credentials: 'include',
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        headers: { 'Content-Type': 'application/json' },
 | 
				
			||||||
 | 
					        body: JSON.stringify({
 | 
				
			||||||
 | 
					            pubkey: key.publicKey,
 | 
				
			||||||
 | 
					            options: options,
 | 
				
			||||||
 | 
					            networks: networks,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(response => {
 | 
				
			||||||
 | 
					        if (!response.ok) {
 | 
				
			||||||
 | 
					            response.text().then(text => {
 | 
				
			||||||
 | 
					                settings.innerHTML = response.status + ' ' + response.statusText + ': ' + text;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return response.text();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(text => {
 | 
				
			||||||
 | 
					        const config = text.replace(/PrivateKey = .*/, "PrivateKey = "+key.privateKey).trim();
 | 
				
			||||||
 | 
					        document.querySelector('code#config').innerHTML = config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const blob = new Blob([config], { type: 'text/plain;charset=utf-8' });
 | 
				
			||||||
 | 
					        const link = document.getElementById('download');
 | 
				
			||||||
 | 
					        link.download = 'wg-fri.conf';
 | 
				
			||||||
 | 
					        link.href = window.URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const qr = qrcode(0, 'L');
 | 
				
			||||||
 | 
					        qr.addData(config.replace(/#.*\n/g, ''));
 | 
				
			||||||
 | 
					        qr.make();
 | 
				
			||||||
 | 
					        document.getElementById('qr').innerHTML = qr.createSvgTag(3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fetchKeys();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					        settings.innerHTML = error;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .finally(() => {
 | 
				
			||||||
 | 
					        form.style.display = 'none';
 | 
				
			||||||
 | 
					        settings.style.display = 'unset';
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
Za VPN oziroma oddeljano povezavo v omrežje FRI uporabljamo <a href="https://wireguard.com">WireGuard</a>. Več informacij o uporabi in nastavitvah VPN najdete v <a href="https://doku.fri.uni-lj.si/vpn">dokumentaciji</a>.
 | 
					Za VPN oziroma oddeljano povezavo v omrežje FRI uporabljamo <a href="https://wireguard.com">WireGuard</a>. Za priklop v omrežje ustvarite nov ključ in prenesite izpisano datoteko. Nato sledite napotkom za posamezni sistem.
 | 
				
			||||||
 | 
					 | 
				
			||||||
<p>
 | 
					 | 
				
			||||||
Za priklop v omrežje spodaj ustvarite nov ključ in prenesite izpisano datoteko. Nato sledite napotkom za posamezni sistem.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
<details>
 | 
					<details>
 | 
				
			||||||
<summary>Windows / Mac</summary>
 | 
					<summary>Windows / Mac</summary>
 | 
				
			||||||
| 
						 | 
					@ -43,14 +40,14 @@ Na vsaki napravi, ki jo želite povezati v omrežje FRI, ustvarite nov ključ. P
 | 
				
			||||||
<input id="name" name="name" pattern="[-._A-Za-z0-9 ]*" maxlength="16" placeholder="A-Z a-z 0-9 . _ - " />
 | 
					<input id="name" name="name" pattern="[-._A-Za-z0-9 ]*" maxlength="16" placeholder="A-Z a-z 0-9 . _ - " />
 | 
				
			||||||
<button id="submit" type="submit">Ustvari ključ</button>
 | 
					<button id="submit" type="submit">Ustvari ključ</button>
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
<input type="checkbox" id="add_default" name="add_default" />
 | 
					<input type="checkbox" id="add-default" name="add-default" />
 | 
				
			||||||
<label for="add_default">Uporabi VPN za ves promet</label>
 | 
					<label for="add-default">Uporabi VPN za ves promet</label>
 | 
				
			||||||
<br>
 | 
					<br>
 | 
				
			||||||
<input type="checkbox" id="use_dns" name="use_dns" checked />
 | 
					<input type="checkbox" id="use-dns" name="use-dns" checked />
 | 
				
			||||||
<label for="use_dns">Uporabi imenske strežnike FRI</label>
 | 
					<label for="use-dns">Uporabi imenske strežnike FRI</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<p>
 | 
					<p>
 | 
				
			||||||
Če vklopite prvo opcijo, bo vaš računalnik čez VPN usmerjal ves promet. Če izklopite drugo opcijo, bodo nekateri strežniki dostopni le prek naslova IP. Če ste v dvomih, pustite privzete nastavitve.
 | 
					Če vklopite prvo opcijo, bo vaš računalnik čez VPN usmerjal ves promet. Če izklopite drugo opcijo, bodo nekateri strežniki dostopni le prek naslova IP. Če ste v dvomih, pustite privzete nastavitve. Več informacij o uporabi in nastavitvah VPN <a href="https://doku.fri.uni-lj.si/vpn">najdemo v dokumentaciji</a>.
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section id="settings" style="display: none;">
 | 
					<section id="settings" style="display: none;">
 | 
				
			||||||
| 
						 | 
					@ -73,125 +70,10 @@ V nastavitvah lahko dodate ali odstranite vnose <code>AllowedIPs</code>. Ti dolo
 | 
				
			||||||
Če ključa ne uporabljamo, smo ga izgubili ali so nam ga ukradli, ga tukaj odstranimo. Trenutno so registrirani ključi:
 | 
					Če ključa ne uporabljamo, smo ga izgubili ali so nam ga ukradli, ga tukaj odstranimo. Trenutno so registrirani ključi:
 | 
				
			||||||
<ul class="keys" style="list-style: none;"></ul>
 | 
					<ul class="keys" style="list-style: none;"></ul>
 | 
				
			||||||
<p class="keys" id="active-key-warning" style="margin-top: 0;">
 | 
					<p class="keys" id="active-key-warning" style="margin-top: 0;">
 | 
				
			||||||
 | 
					<font color="red"><sup>★</sup></font> Ta ključ uporablja trenutna povezava. Če ga odstranite, bo prekinjena.
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script type="text/javascript" src="{{ url_for('static', filename='qrcode.js') }}"></script>
 | 
					<script type="text/javascript" src="{{ url_for('static', filename='qrcode.js') }}"></script>
 | 
				
			||||||
<script type="text/javascript" src="{{ url_for('static', filename='wireguard.js') }}"></script>
 | 
					<script type="text/javascript" src="{{ url_for('static', filename='wireguard.js') }}"></script>
 | 
				
			||||||
<script type="text/javascript">
 | 
					<script type="text/javascript" src="{{ url_for('static', filename='vpn.js') }}"></script>
 | 
				
			||||||
function del_key(key) {
 | 
					 | 
				
			||||||
    fetch('del', {
 | 
					 | 
				
			||||||
        credentials: 'include',
 | 
					 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: { 'Content-Type': 'application/json' },
 | 
					 | 
				
			||||||
        body: JSON.stringify({ pubkey: key })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(response => {
 | 
					 | 
				
			||||||
        if (!response.ok)
 | 
					 | 
				
			||||||
            throw new Error('deleting key failed');
 | 
					 | 
				
			||||||
        return response.text();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(data => {
 | 
					 | 
				
			||||||
        // reload key list
 | 
					 | 
				
			||||||
        window.dispatchEvent(new Event('load'));
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(error => console.error(error));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function fetch_keys() {
 | 
					 | 
				
			||||||
    fetch('list', {
 | 
					 | 
				
			||||||
        credentials: 'include'
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(response => {
 | 
					 | 
				
			||||||
        if (!response.ok)
 | 
					 | 
				
			||||||
            throw new Error('fetching keys failed');
 | 
					 | 
				
			||||||
        return response.json();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(data => {
 | 
					 | 
				
			||||||
        const keys = document.querySelector('ul.keys');
 | 
					 | 
				
			||||||
        keys.innerHTML = '';
 | 
					 | 
				
			||||||
        const warning = document.getElementById('active-key-warning');
 | 
					 | 
				
			||||||
        warning.innerHTML = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (let key of Object.values(data)) {
 | 
					 | 
				
			||||||
            var a = document.createElement('a');
 | 
					 | 
				
			||||||
            a.innerText = '✖';
 | 
					 | 
				
			||||||
            a.href = '';
 | 
					 | 
				
			||||||
            a.addEventListener('click', event => {
 | 
					 | 
				
			||||||
                del_key(key.key);
 | 
					 | 
				
			||||||
                event.preventDefault();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            var li = document.createElement('li');
 | 
					 | 
				
			||||||
            li.innerHTML = ' ' + (new Date(key.time*1000).toISOString().split('T')[0]) +
 | 
					 | 
				
			||||||
		' <code>' + key.key + '</code> ' + key.name +
 | 
					 | 
				
			||||||
                (key.active ? '<font color="red"><sup>★</sup></font> ' : '');
 | 
					 | 
				
			||||||
            li.prepend(a);
 | 
					 | 
				
			||||||
            keys.appendChild(li);
 | 
					 | 
				
			||||||
            if (key.active)
 | 
					 | 
				
			||||||
                warning.innerHTML = '<font color="red"><sup>★</sup></font> Ta ključ uporablja trenutna povezava. Če ga odstranite, bo prekinjena.';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        document.querySelector('section.keys').style.display = (Object.keys(data).length ? 'unset' : 'none');
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(error => console.error(error));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.addEventListener('load', fetch_keys);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const request = document.getElementById('request');
 | 
					 | 
				
			||||||
request.addEventListener('submit', event => {
 | 
					 | 
				
			||||||
    event.preventDefault();
 | 
					 | 
				
			||||||
    const name = document.getElementById('name');
 | 
					 | 
				
			||||||
    const key = wireguard.generateKeypair();
 | 
					 | 
				
			||||||
    const settings = document.getElementById('settings');
 | 
					 | 
				
			||||||
    const submit = document.getElementById('submit');
 | 
					 | 
				
			||||||
    const use_dns = document.getElementById('use_dns');
 | 
					 | 
				
			||||||
    const add_default = document.getElementById('add_default');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    submit.innerHTML = 'Obdelovanje…';
 | 
					 | 
				
			||||||
    submit.disabled = true;
 | 
					 | 
				
			||||||
    fetch('new', {
 | 
					 | 
				
			||||||
        credentials: 'include',
 | 
					 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: { 'Content-Type': 'application/json' },
 | 
					 | 
				
			||||||
        body: JSON.stringify({
 | 
					 | 
				
			||||||
            pubkey: key.publicKey,
 | 
					 | 
				
			||||||
            name: name.value,
 | 
					 | 
				
			||||||
            use_dns: use_dns.checked,
 | 
					 | 
				
			||||||
            add_default: add_default.checked,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(response => {
 | 
					 | 
				
			||||||
        if (!response.ok) {
 | 
					 | 
				
			||||||
            response.text().then(text => {
 | 
					 | 
				
			||||||
                settings.innerHTML = response.status + ' ' + response.statusText + ': ' + text;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            return response.text();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .then(text => {
 | 
					 | 
				
			||||||
        var complete = text.replace(/PrivateKey = .*/, "PrivateKey = "+key.privateKey).trim();
 | 
					 | 
				
			||||||
        document.getElementById("config").innerHTML = complete;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var blob = new Blob([complete], { type: 'text/plain;charset=utf-8' });
 | 
					 | 
				
			||||||
        const link = document.getElementById('download');
 | 
					 | 
				
			||||||
        link.download = 'wg-fri.conf';
 | 
					 | 
				
			||||||
        link.href = window.URL.createObjectURL(blob);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var qr = qrcode(0, 'L');
 | 
					 | 
				
			||||||
        qr.addData(complete.replace(/#.*\n/g, ''));
 | 
					 | 
				
			||||||
        qr.make();
 | 
					 | 
				
			||||||
        document.getElementById('qr').innerHTML = qr.createSvgTag(3);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // reload key list
 | 
					 | 
				
			||||||
        fetch_keys();
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(error => {
 | 
					 | 
				
			||||||
        settings.innerHTML = error;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .finally(() => {
 | 
					 | 
				
			||||||
        request.style.display = 'none';
 | 
					 | 
				
			||||||
        settings.style.display = 'unset';
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										55
									
								
								web/vpn.py
									
										
									
									
									
								
							
							
						
						
									
										55
									
								
								web/vpn.py
									
										
									
									
									
								
							| 
						 | 
					@ -21,20 +21,22 @@ def index():
 | 
				
			||||||
@blueprint.route('/list')
 | 
					@blueprint.route('/list')
 | 
				
			||||||
@flask_login.login_required
 | 
					@flask_login.login_required
 | 
				
			||||||
def list():
 | 
					def list():
 | 
				
			||||||
 | 
					    # Return logged-in user’s keys, marking the key used for current connection (if any).
 | 
				
			||||||
    user = flask_login.current_user.get_id()
 | 
					    user = flask_login.current_user.get_id()
 | 
				
			||||||
    return flask.jsonify(
 | 
					    return flask.jsonify({
 | 
				
			||||||
        {k: v | {'active': flask.request.remote_addr in (v.get('ip'), v.get('ip6'))}
 | 
					        ip: data | {'active': flask.request.remote_addr in (ip, data.get('ip6'))}
 | 
				
			||||||
         for k, v in db.load('wireguard').items() if v.get('user') == user})
 | 
					            for ip, data in db.load('wireguard').items() if data.get('user') == user
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@blueprint.route('/new', methods=('POST',))
 | 
					@blueprint.route('/new', methods=('POST',))
 | 
				
			||||||
@flask_login.login_required
 | 
					@flask_login.login_required
 | 
				
			||||||
def new():
 | 
					def new():
 | 
				
			||||||
    # Each key is associated with a new IPv4 address from the pool settings['wg_net'].
 | 
					    # Each key is assigned a new IPv4 address from the pool settings['wg_net'].
 | 
				
			||||||
    # Each key gets an IPv6 subnet depending on the amount of surplus addresses available.
 | 
					    # Each key is then assigned a corresponding IPv6 subnet, depending on the amount of surplus addresses available.
 | 
				
			||||||
    # For wg_net 10.10.0.0/18 and wg_net6 1234:5678:90ab:cdef::/64,
 | 
					    # For wg_net 10.10.0.0/18 and wg_net6 1234:5678:90ab:cdef::/64,
 | 
				
			||||||
    # the key for 10.10.0.10/32 would get 1234:5678:90ab:cdef:a::/80.
 | 
					    # the key for 10.10.0.10/32 would get 1234:5678:90ab:cdef:a::/80.
 | 
				
			||||||
    def ipv4to6(net4, ip4, net6):
 | 
					    def ipv4to6(net4, ip4, net6):
 | 
				
			||||||
        # Calculate the address and prefix length for the assigned IPv6 network.
 | 
					        # Calculate the address and prefix length for the IPv6 network that can be assigned to this key.
 | 
				
			||||||
        len4 = (net4.max_prefixlen - net4.prefixlen)
 | 
					        len4 = (net4.max_prefixlen - net4.prefixlen)
 | 
				
			||||||
        len6 = (net6.max_prefixlen - net6.prefixlen)
 | 
					        len6 = (net6.max_prefixlen - net6.prefixlen)
 | 
				
			||||||
        # Make sure the network address ends at a colon. Wastes some addresses but IPv6.
 | 
					        # Make sure the network address ends at a colon. Wastes some addresses but IPv6.
 | 
				
			||||||
| 
						 | 
					@ -43,35 +45,39 @@ def new():
 | 
				
			||||||
        return ip6 + '/' + str(net6.max_prefixlen - assigned)
 | 
					        return ip6 + '/' + str(net6.max_prefixlen - assigned)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pubkey = flask.request.json.get('pubkey', '')
 | 
					    pubkey = flask.request.json.get('pubkey', '')
 | 
				
			||||||
 | 
					    options = flask.request.json.get('options', {})
 | 
				
			||||||
    if not re.match(wgkey_regex, pubkey):
 | 
					    if not re.match(wgkey_regex, pubkey):
 | 
				
			||||||
        return flask.Response('invalid key', status=400, mimetype='text/plain')
 | 
					        return flask.Response('invalid key', status=400, mimetype='text/plain')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Read server’s private key and find the corresponding public key.
 | 
				
			||||||
    settings = db.load('settings')
 | 
					    settings = db.load('settings')
 | 
				
			||||||
    server_pubkey = subprocess.run([f'wg pubkey'], input=settings.get('wg_key'),
 | 
					    server_pubkey = subprocess.run([f'wg pubkey'], input=settings.get('wg_key'),
 | 
				
			||||||
            text=True, capture_output=True, shell=True).stdout.strip()
 | 
					            text=True, capture_output=True, shell=True).stdout.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    now = datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					    key = {
 | 
				
			||||||
 | 
					        'key': pubkey,
 | 
				
			||||||
 | 
					        'time': now.timestamp(),
 | 
				
			||||||
 | 
					        'name': re.sub('[^-._A-Za-z0-9]', '', options.get('name', '')),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Determine IPv4 and IPv6 addresses for the new key.
 | 
				
			||||||
    host = ipaddress.ip_interface(settings.get('wg_net') or '10.0.0.1/24')
 | 
					    host = ipaddress.ip_interface(settings.get('wg_net') or '10.0.0.1/24')
 | 
				
			||||||
    ip6 = None
 | 
					    key['ip6'] = None
 | 
				
			||||||
    with db.locked():
 | 
					    with db.locked():
 | 
				
			||||||
        # Find a free address for the new key.
 | 
					        # Find a free address for the new key.
 | 
				
			||||||
        keys = db.read('wireguard')
 | 
					        keys = db.read('wireguard')
 | 
				
			||||||
        for index, ip in enumerate(host.network.hosts(), start=1):
 | 
					        for index, ip in enumerate(host.network.hosts(), start=1):
 | 
				
			||||||
            if ip != host.ip and str(ip) not in keys:
 | 
					            if ip != host.ip and str(ip) not in keys:
 | 
				
			||||||
                if wg_net6 := settings.get('wg_net6'):
 | 
					                if wg_net6 := settings.get('wg_net6'):
 | 
				
			||||||
                    ip6 = ipv4to6(host.network, ip, ipaddress.ip_interface(wg_net6).network)
 | 
					                    key['ip6'] = ipv4to6(host.network, ip, ipaddress.ip_interface(wg_net6).network)
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return flask.Response('no more available IP addresses', status=500, mimetype='text/plain')
 | 
					            return flask.Response('no more available IP addresses', status=500, mimetype='text/plain')
 | 
				
			||||||
        now = datetime.datetime.utcnow()
 | 
					 | 
				
			||||||
        name = re.sub('[^-._A-Za-z0-9]', '', flask.request.json.get('name', ''))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        keys[str(ip)] = {
 | 
					        # Add remaining attributes to new key and update key database.
 | 
				
			||||||
            'key': pubkey,
 | 
					        key['user'] = flask_login.current_user.get_id()
 | 
				
			||||||
            'ip6': str(ip6) if ip6 else None,
 | 
					        keys[str(ip)] = key
 | 
				
			||||||
            'time': now.timestamp(),
 | 
					 | 
				
			||||||
            'user': flask_login.current_user.get_id(),
 | 
					 | 
				
			||||||
            'name': name,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        db.write('wireguard', keys)
 | 
					        db.write('wireguard', keys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Generate a new config archive for firewall nodes.
 | 
					    # Generate a new config archive for firewall nodes.
 | 
				
			||||||
| 
						 | 
					@ -83,13 +89,13 @@ def new():
 | 
				
			||||||
        'port': settings.get('wg_port') or '51820',
 | 
					        'port': settings.get('wg_port') or '51820',
 | 
				
			||||||
        'server_key': server_pubkey,
 | 
					        'server_key': server_pubkey,
 | 
				
			||||||
        'pubkey': pubkey,
 | 
					        'pubkey': pubkey,
 | 
				
			||||||
        'ip': str(ip),
 | 
					 | 
				
			||||||
        'ip6': str(ip6) if ip6 else None,
 | 
					 | 
				
			||||||
        'timestamp': now,
 | 
					        'timestamp': now,
 | 
				
			||||||
        'name': name,
 | 
					        'ip': str(ip),
 | 
				
			||||||
        'dns': settings.get('wg_dns') if flask.request.json.get('use_dns', True) else False,
 | 
					        'ip6': key['ip6'],
 | 
				
			||||||
 | 
					        'name': key['name'],
 | 
				
			||||||
 | 
					        'dns': settings.get('wg_dns', '') if options.get('use-dns', True) else False,
 | 
				
			||||||
        'allowed_nets': settings.get('wg_allowed_nets', ''),
 | 
					        'allowed_nets': settings.get('wg_allowed_nets', ''),
 | 
				
			||||||
        'add_default': flask.request.json.get('add_default', False),
 | 
					        'add_default': options.get('add-default', False),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return flask.render_template('vpn/wg-fri.conf', **args)
 | 
					    return flask.render_template('vpn/wg-fri.conf', **args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +108,10 @@ def delete():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with db.locked():
 | 
					    with db.locked():
 | 
				
			||||||
        user = flask_login.current_user.get_id()
 | 
					        user = flask_login.current_user.get_id()
 | 
				
			||||||
        keys = {k: v for k, v in db.read('wireguard').items() if v.get('user') != user or v.get('key') != pubkey}
 | 
					        keys = {
 | 
				
			||||||
 | 
					            ip: data for ip, data in db.read('wireguard').items()
 | 
				
			||||||
 | 
					                if not (data.get('key') == pubkey and (data.get('user') == user or flask_login.current_user.is_admin))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        db.write('wireguard', keys)
 | 
					        db.write('wireguard', keys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    system.run(system.save_config)
 | 
					    system.run(system.save_config)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue