1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 15:20:09 +01:00

Finished restrict & allow access of peers

This commit is contained in:
Donald Zou 2024-06-02 23:22:43 +08:00
parent c7ca20b45a
commit 9a280e99ad
7 changed files with 298 additions and 123 deletions

View File

@ -238,6 +238,12 @@ class WireguardConfiguration:
self.Status = self.Name in psutil.net_if_addrs().keys() self.Status = self.Name in psutil.net_if_addrs().keys()
return self.Status return self.Status
def __getRestrictedPeers(self):
self.RestrictedPeers = []
restricted = cursor.execute("SELECT * FROM %s_restrict_access" % self.Name).fetchall()
for i in restricted:
self.RestrictedPeers.append(Peer(i, self))
def __getPeers(self): def __getPeers(self):
self.Peers = [] self.Peers = []
with open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'), 'r') as configFile: with open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'), 'r') as configFile:
@ -309,6 +315,55 @@ class WireguardConfiguration:
return True, i return True, i
return False, None return False, None
def allowAccessPeers(self, listOfPublicKeys):
# numOfAllowedPeers = 0
# numOfFailedToAllowPeers = 0
for i in listOfPublicKeys:
p = cursor.execute("SELECT * FROM %s_restrict_access WHERE id = ?" % self.Name, (i,)).fetchone()
if p is not None:
cursor.execute("INSERT INTO %s SELECT * FROM %s_restrict_access WHERE id = ?"
% (self.Name, self.Name,), (p['id'],))
cursor.execute("DELETE FROM %s_restrict_access WHERE id = ?"
% self.Name, (p['id'],))
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
shell=True, stderr=subprocess.STDOUT)
else:
return ResponseObject(False, "Failed to allow access of peer " + i)
if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard")
self.__getPeers()
return ResponseObject(True, "Allow access successfully!")
def restrictPeers(self, listOfPublicKeys):
numOfRestrictedPeers = 0
numOfFailedToRestrictPeers = 0
for p in listOfPublicKeys:
found, pf = self.searchPeer(p)
if found:
try:
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
shell=True, stderr=subprocess.STDOUT)
cursor.execute("INSERT INTO %s_restrict_access SELECT * FROM %s WHERE id = ?" %
(self.Name, self.Name,), (pf.id,))
cursor.execute("UPDATE %s_restrict_access SET status = 'stopped' WHERE id = ?" %
(self.Name,), (pf.id,))
cursor.execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
numOfRestrictedPeers += 1
except Exception as e:
numOfFailedToRestrictPeers += 1
if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard")
self.__getPeers()
if numOfRestrictedPeers == len(listOfPublicKeys):
return ResponseObject(True, f"Restricted {numOfRestrictedPeers} peer(s)")
return ResponseObject(False,
f"Restricted {numOfRestrictedPeers} peer(s) successfully. Failed to restrict {numOfFailedToRestrictPeers} peer(s)")
pass
def deletePeers(self, listOfPublicKeys): def deletePeers(self, listOfPublicKeys):
numOfDeletedPeers = 0 numOfDeletedPeers = 0
numOfFailedToDeletePeers = 0 numOfFailedToDeletePeers = 0
@ -468,6 +523,10 @@ class WireguardConfiguration:
self.__getPeers() self.__getPeers()
return self.Peers return self.Peers
def getRestrictedPeersList(self) -> list:
self.__getRestrictedPeers()
return self.RestrictedPeers
def toJson(self): def toJson(self):
self.Status = self.getStatus() self.Status = self.getStatus()
return { return {
@ -1047,6 +1106,30 @@ def API_deletePeers(configName: str) -> ResponseObject:
return ResponseObject(False, "Configuration does not exist") return ResponseObject(False, "Configuration does not exist")
@app.route('/api/restrictPeers/<configName>', methods=['POST'])
def API_restrictPeers(configName: str) -> ResponseObject:
data = request.get_json()
peers = data['peers']
if configName in WireguardConfigurations.keys():
if len(peers) == 0:
return ResponseObject(False, "Please specify more than one peer")
configuration = WireguardConfigurations.get(configName)
return configuration.restrictPeers(peers)
return ResponseObject(False, "Configuration does not exist")
@app.route('/api/allowAccessPeers/<configName>', methods=['POST'])
def API_allowAccessPeers(configName: str) -> ResponseObject:
data = request.get_json()
peers = data['peers']
if configName in WireguardConfigurations.keys():
if len(peers) == 0:
return ResponseObject(False, "Please specify more than one peer")
configuration = WireguardConfigurations.get(configName)
return configuration.allowAccessPeers(peers)
return ResponseObject(False, "Configuration does not exist")
@app.route('/api/addPeers/<configName>', methods=['POST']) @app.route('/api/addPeers/<configName>', methods=['POST'])
def API_addPeers(configName): def API_addPeers(configName):
data = request.get_json() data = request.get_json()
@ -1184,7 +1267,8 @@ def API_getConfigurationInfo():
return ResponseObject(False, "Please provide configuration name") return ResponseObject(False, "Please provide configuration name")
return ResponseObject(data={ return ResponseObject(data={
"configurationInfo": WireguardConfigurations[configurationName], "configurationInfo": WireguardConfigurations[configurationName],
"configurationPeers": WireguardConfigurations[configurationName].getPeersList() "configurationPeers": WireguardConfigurations[configurationName].getPeersList(),
"configurationRestrictedPeers": WireguardConfigurations[configurationName].getRestrictedPeersList()
}) })

View File

@ -34,22 +34,31 @@ export default {
</script> </script>
<template> <template>
<div class="card shadow-sm rounded-3"> <div class="card shadow-sm rounded-3"
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0"> :class="{'border-warning': Peer.restricted}"
<div class="dot ms-0" :class="{active: Peer.status === 'running'}"></div> >
<div style="font-size: 0.8rem" class="ms-auto d-flex gap-2"> <div>
<span class="text-primary"> <div v-if="!Peer.restricted" class="card-header bg-transparent d-flex align-items-center gap-2 border-0">
<i class="bi bi-arrow-down"></i><strong> <div class="dot ms-0" :class="{active: Peer.status === 'running'}"></div>
{{(Peer.cumu_receive + Peer.total_receive).toFixed(4)}}</strong> GB <div style="font-size: 0.8rem" class="ms-auto d-flex gap-2">
</span> <span class="text-primary">
<span class="text-success"> <i class="bi bi-arrow-down"></i><strong>
<i class="bi bi-arrow-up"></i><strong> {{(Peer.cumu_receive + Peer.total_receive).toFixed(4)}}</strong> GB
{{(Peer.cumu_sent + Peer.total_sent).toFixed(4)}}</strong> GB </span>
</span> <span class="text-success">
<span class="text-secondary" v-if="Peer.latest_handshake !== 'No Handshake'"> <i class="bi bi-arrow-up"></i><strong>
<i class="bi bi-arrows-angle-contract"></i> {{(Peer.cumu_sent + Peer.total_sent).toFixed(4)}}</strong> GB
{{getLatestHandshake}} ago </span>
</span> <span class="text-secondary" v-if="Peer.latest_handshake !== 'No Handshake'">
<i class="bi bi-arrows-angle-contract"></i>
{{getLatestHandshake}} ago
</span>
</div>
</div>
<div v-else class="border-0 card-header bg-transparent text-warning fw-bold"
style="font-size: 0.8rem">
<i class="bi-lock-fill me-2"></i>
Access Restricted
</div> </div>
</div> </div>
<div class="card-body pt-1" style="font-size: 0.9rem"> <div class="card-body pt-1" style="font-size: 0.9rem">

View File

@ -118,15 +118,7 @@ export default {
} }
}, },
mounted() { mounted() {
// console.log('mounted')
// this.loading = true;
// let id = this.$route.params.id;
// this.configurationInfo = [];
// this.configurationPeers = [];
// if (id){
// this.getPeers(id)
// this.setInterval();
// }
}, },
watch: { watch: {
'$route': { '$route': {
@ -176,6 +168,13 @@ export default {
}, (res) => { }, (res) => {
this.configurationInfo = res.data.configurationInfo; this.configurationInfo = res.data.configurationInfo;
this.configurationPeers = res.data.configurationPeers; this.configurationPeers = res.data.configurationPeers;
this.configurationPeers.forEach(x => {
x.restricted = false;
})
res.data.configurationRestrictedPeers.forEach(x => {
x.restricted = true;
this.configurationPeers.push(x)
})
this.loading = false; this.loading = false;
if (this.configurationPeers.length > 0){ if (this.configurationPeers.length > 0){
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((x,y) => x + y).toFixed(4); const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((x,y) => x + y).toFixed(4);
@ -336,7 +335,22 @@ export default {
keys: ["name", "id", "allowed_ip"] keys: ["name", "id", "allowed_ip"]
}); });
const result = this.wireguardConfigurationStore.searchString ? fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers; const result = this.wireguardConfigurationStore.searchString ?
fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers;
if (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){
return result.slice().sort((a, b) => {
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
return 1;
}
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
return -1;
}
return 0;
});
}
return result.slice().sort((a, b) => { return result.slice().sort((a, b) => {
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
@ -460,7 +474,7 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<div class="row gx-2 gy-2 mb-5"> <div class="row gx-2 gy-2 mb-3">
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px"> <div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div> <div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div>
@ -498,14 +512,9 @@ export default {
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<div class="d-flex align-items-center gap-3 mb-2 "> <!-- <div class="d-flex align-items-center gap-3 mb-2">-->
<h3>Peers</h3> <!-- <h3>Peers</h3>-->
<!-- </div>-->
<RouterLink
to="create"
class="text-decoration-none ms-auto btn btn-primary rounded-3">
<i class="bi bi-plus-circle-fill me-2"></i>Peers</RouterLink>
</div>
<PeerSearch></PeerSearch> <PeerSearch></PeerSearch>
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0"> <TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
<div class="col-12 col-lg-6 col-xl-4" <div class="col-12 col-lg-6 col-xl-4"

View File

@ -18,7 +18,8 @@ export default {
sort: { sort: {
status: "Status", status: "Status",
name: "Name", name: "Name",
allowed_ip: "Allowed IP" allowed_ip: "Allowed IP",
restricted: "Restricted"
}, },
interval: { interval: {
'5000': '5 Seconds', '5000': '5 Seconds',
@ -59,47 +60,59 @@ export default {
</script> </script>
<template> <template>
<div class="d-flex gap-2 mb-3 z-3"> <div>
<div class="dropdown"> <div class="d-flex gap-2 mb-3 z-3">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <h4 class="mb-0">Peers</h4>
<i class="bi bi-filter-circle me-2"></i> <RouterLink
Sort to="create"
</button> class=" ms-auto text-decoration-none btn btn-primary rounded-3 btn-sm">
<ul class="dropdown-menu mt-2 shadow rounded-3"> <i class="bi bi-plus-lg me-2"></i>Peers
<li v-for="(value, key) in this.sort"> </RouterLink>
<a class="dropdown-item d-flex" role="button" @click="this.updateSort(key)">
<span class="me-auto">{{value}}</span> <div class="dropdown">
<i class="bi bi-check text-primary" <button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false">
v-if="store.Configuration.Server.dashboard_sort === key"></i> <i class="bi bi-filter-circle me-2"></i>
</a> Sort
</li> </button>
</ul> <ul class="dropdown-menu mt-2 shadow rounded-3">
</div> <li v-for="(value, key) in this.sort">
<div class="dropdown"> <a class="dropdown-item d-flex" role="button" @click="this.updateSort(key)">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <span class="me-auto">{{value}}</span>
<i class="bi bi-arrow-repeat me-2"></i>Refresh Interval <i class="bi bi-check text-primary"
</button> v-if="store.Configuration.Server.dashboard_sort === key"></i>
<ul class="dropdown-menu shadow mt-2 rounded-3"> </a>
<li v-for="(value, key) in this.interval"> </li>
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)"> </ul>
<span class="me-auto">{{value}}</span> </div>
<i class="bi bi-check text-primary" <div class="dropdown">
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i> <button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false">
</a></li> <i class="bi bi-arrow-repeat me-2"></i>Refresh Interval
</ul> </button>
</div> <ul class="dropdown-menu shadow mt-2 rounded-3">
<!-- <button class="btn btn-outline-secondary btn-sm rounded-3" type="button"--> <li v-for="(value, key) in this.interval">
<!-- @click="this.store.Peers.Selecting = !this.store.Peers.Selecting"--> <a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
<!-- >--> <span class="me-auto">{{value}}</span>
<!-- <i class="bi bi-app-indicator me-2"></i>--> <i class="bi bi-check text-primary"
<!-- Select--> v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
<!-- </button>--> </a></li>
</ul>
</div>
<!-- <button class="btn btn-outline-secondary btn-sm rounded-3" type="button"-->
<!-- @click="this.store.Peers.Selecting = !this.store.Peers.Selecting"-->
<!-- >-->
<!-- <i class="bi bi-app-indicator me-2"></i>-->
<!-- Select-->
<!-- </button>-->
<div class="d-flex align-items-center">
<!-- <label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>-->
<input class="form-control form-control-sm rounded-3"
placeholder="Search..."
id="searchPeers"
v-model="this.wireguardConfigurationStore.searchString">
</div>
<div class="ms-auto d-flex align-items-center">
<label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>
<input class="form-control form-control-sm rounded-3"
id="searchPeers"
v-model="this.wireguardConfigurationStore.searchString">
</div> </div>
</div> </div>

View File

@ -13,7 +13,9 @@ export default {
}, },
data(){ data(){
return{ return{
deleteBtnDisabled: false deleteBtnDisabled: false,
restrictBtnDisabled: false,
allowAccessBtnDisabled: false,
} }
}, },
methods: { methods: {
@ -55,6 +57,26 @@ export default {
this.$emit("refresh") this.$emit("refresh")
this.deleteBtnDisabled = false this.deleteBtnDisabled = false
}) })
},
restrictPeer(){
this.restrictBtnDisabled = true
fetchPost(`/api/restrictPeers/${this.$route.params.id}`, {
peers: [this.Peer.id]
}, (res) => {
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
this.$emit("refresh")
this.restrictBtnDisabled = false
})
},
allowAccessPeer(){
this.allowAccessBtnDisabled = true
fetchPost(`/api/allowAccessPeers/${this.$route.params.id}`, {
peers: [this.Peer.id]
}, (res) => {
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
this.$emit("refresh")
this.allowAccessBtnDisabled = false
})
} }
} }
} }
@ -62,61 +84,75 @@ export default {
<template> <template>
<ul class="dropdown-menu mt-2 shadow-lg d-block rounded-3" style="max-width: 200px"> <ul class="dropdown-menu mt-2 shadow-lg d-block rounded-3" style="max-width: 200px">
<template v-if="!this.Peer.private_key"> <template v-if="!this.Peer.restricted">
<li> <template v-if="!this.Peer.private_key">
<small class="w-100 dropdown-item text-muted" <li>
style="white-space: break-spaces; font-size: 0.7rem" <small class="w-100 dropdown-item text-muted"
>Download & QR Code is not available due to no <code>private key</code> style="white-space: break-spaces; font-size: 0.7rem"
set for this peer >Download & QR Code is not available due to no <code>private key</code>
</small> set for this peer
</li> </small>
<li><hr class="dropdown-divider"></li> </li>
</template> <li><hr class="dropdown-divider"></li>
</template>
<li>
<a class="dropdown-item d-flex" role="button"
@click="this.$emit('setting')"
>
<i class="me-auto bi bi-pen"></i> Edit
</a>
</li>
<template v-if="this.Peer.private_key">
<li>
<a class="dropdown-item d-flex" role="button" @click="this.downloadPeer()">
<i class="me-auto bi bi-download"></i> Download
</a>
</li>
<li> <li>
<a class="dropdown-item d-flex" role="button" <a class="dropdown-item d-flex" role="button"
@click="this.downloadQRCode()" @click="this.$emit('setting')"
> >
<i class="me-auto bi bi-qr-code"></i> QR Code <i class="me-auto bi bi-pen"></i> Edit
</a>
</li>
<template v-if="this.Peer.private_key">
<li>
<a class="dropdown-item d-flex" role="button" @click="this.downloadPeer()">
<i class="me-auto bi bi-download"></i> Download
</a>
</li>
<li>
<a class="dropdown-item d-flex" role="button"
@click="this.downloadQRCode()"
>
<i class="me-auto bi bi-qr-code"></i> QR Code
</a>
</li>
</template>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex text-warning"
@click="this.restrictPeer()"
:class="{disabled: this.restrictBtnDisabled}"
role="button">
<i class="me-auto bi bi-lock"></i> {{!this.restrictBtnDisabled ? "Restrict Access":"Restricting..."}}
</a>
</li>
<li>
<a class="dropdown-item d-flex fw-bold text-danger"
@click="this.deletePeer()"
:class="{disabled: this.deleteBtnDisabled}"
role="button">
<i class="me-auto bi bi-trash"></i> {{!this.deleteBtnDisabled ? "Delete":"Deleting..."}}
</a>
</li>
</template>
<template v-else>
<li>
<a class="dropdown-item d-flex text-warning"
@click="this.allowAccessPeer()"
:class="{disabled: this.restrictBtnDisabled}"
role="button">
<i class="me-auto bi bi-unlock"></i>
{{!this.allowAccessBtnDisabled ? "Allow Access":"Allowing..."}}
</a> </a>
</li> </li>
</template> </template>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex text-warning"
role="button">
<i class="me-auto bi bi-lock"></i> Lock
</a>
</li>
<li>
<a class="dropdown-item d-flex fw-bold text-danger"
@click="this.deletePeer()"
:class="{disabled: this.deleteBtnDisabled}"
role="button">
<i class="me-auto bi bi-trash"></i> {{!this.deleteBtnDisabled ? "Delete":"Deleting..."}}
</a>
</li>
</ul> </ul>
</template> </template>
<style scoped> <style scoped>
.dropdown-menu{ .dropdown-menu{
right: 1rem; right: 1rem;
min-width: 200px;
} }
.dropdown-item.disabled, .dropdown-item:disabled{ .dropdown-item.disabled, .dropdown-item:disabled{

View File

@ -0,0 +1,23 @@
<script>
export default {
name: "restrictedPeers"
}
</script>
<template>
<div class="container">
<div class="mb-4">
<RouterLink to="peers" is="div" class="d-flex align-items-center gap-4 text-decoration-none">
<h3 class="mb-0 text-body">
<i class="bi bi-chevron-left"></i>
</h3>
<h3 class="text-body mb-0">Restricted Peers</h3>
</RouterLink>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -14,6 +14,7 @@ import Configuration from "@/views/configuration.vue";
import PeerSettings from "@/components/configurationComponents/peerSettings.vue"; import PeerSettings from "@/components/configurationComponents/peerSettings.vue";
import PeerList from "@/components/configurationComponents/peerList.vue"; import PeerList from "@/components/configurationComponents/peerList.vue";
import PeerCreate from "@/components/configurationComponents/peerCreate.vue"; import PeerCreate from "@/components/configurationComponents/peerCreate.vue";
import RestrictedPeers from "@/components/configurationComponents/restrictedPeers.vue";
const checkAuth = async () => { const checkAuth = async () => {
let result = false let result = false