mirror of
https://github.com/h44z/wg-portal.git
synced 2025-08-10 23:42:24 +00:00
peer creation and deletion
This commit is contained in:
parent
e38c48bede
commit
4a53a5207a
@ -69,7 +69,6 @@ func main() {
|
|||||||
|
|
||||||
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager,
|
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager,
|
||||||
statisticsCollector, templateManager)
|
statisticsCollector, templateManager)
|
||||||
|
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
err = backend.Startup(ctx)
|
err = backend.Startup(ctx)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
@ -5,8 +5,10 @@ import {computed, ref, watch} from "vue";
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import Vue3TagsInput from 'vue3-tags-input';
|
import Vue3TagsInput from 'vue3-tags-input';
|
||||||
|
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||||
import isCidr from "is-cidr";
|
import isCidr from "is-cidr";
|
||||||
import {isIP} from 'is-ip';
|
import {isIP} from 'is-ip';
|
||||||
|
import { freshInterface } from '@/helpers/models';
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -34,54 +36,10 @@ const title = computed(() => {
|
|||||||
return t("interfaces.interface.new")
|
return t("interfaces.interface.new")
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = ref(freshFormData())
|
const formData = ref(freshInterface())
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
function freshFormData() {
|
|
||||||
return {
|
|
||||||
Disabled: false,
|
|
||||||
DisplayName: "",
|
|
||||||
Identifier: "",
|
|
||||||
Mode: "server",
|
|
||||||
|
|
||||||
PublicKey: "",
|
|
||||||
PrivateKey: "",
|
|
||||||
|
|
||||||
ListenPort: 51820,
|
|
||||||
Addresses: [],
|
|
||||||
DnsStr: [],
|
|
||||||
DnsSearch: [],
|
|
||||||
|
|
||||||
Mtu: 0,
|
|
||||||
FirewallMark: 0,
|
|
||||||
RoutingTable: "",
|
|
||||||
|
|
||||||
PreUp: "",
|
|
||||||
PostUp: "",
|
|
||||||
PreDown: "",
|
|
||||||
PostDown: "",
|
|
||||||
|
|
||||||
SaveConfig: false,
|
|
||||||
|
|
||||||
// Peer defaults
|
|
||||||
|
|
||||||
PeerDefNetwork: [],
|
|
||||||
PeerDefDns: [],
|
|
||||||
PeerDefDnsSearch: [],
|
|
||||||
PeerDefEndpoint: "",
|
|
||||||
PeerDefAllowedIPs: [],
|
|
||||||
PeerDefMtu: 0,
|
|
||||||
PeerDefPersistentKeepalive: 0,
|
|
||||||
PeerDefFirewallMark: 0,
|
|
||||||
PeerDefRoutingTable: "",
|
|
||||||
PeerDefPreUp: "",
|
|
||||||
PeerDefPostUp: "",
|
|
||||||
PeerDefPreDown: "",
|
|
||||||
PeerDefPostDown: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.visible, async (newValue, oldValue) => {
|
watch(() => props.visible, async (newValue, oldValue) => {
|
||||||
if (oldValue === false && newValue === true) { // if modal is shown
|
if (oldValue === false && newValue === true) { // if modal is shown
|
||||||
console.log(selectedInterface.value)
|
console.log(selectedInterface.value)
|
||||||
@ -170,7 +128,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
formData.value = freshFormData()
|
formData.value = freshInterface()
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,20 +222,7 @@ function handleChangePeerDefDns(tags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleChangePeerDefDnsSearch(tags) {
|
function handleChangePeerDefDnsSearch(tags) {
|
||||||
formData.value.DnsSearch = tags
|
formData.value.PeerDefDnsSearch = tags
|
||||||
}
|
|
||||||
|
|
||||||
function validateCIDR(value) {
|
|
||||||
return isCidr(value) !== 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateIP(value) {
|
|
||||||
return isIP(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateDomain(value) {
|
|
||||||
console.log("validating: ", value)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
@ -289,6 +234,7 @@ async function save() {
|
|||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
notify({
|
notify({
|
||||||
title: "Backend Connection Failure",
|
title: "Backend Connection Failure",
|
||||||
text: "Failed to save interface!",
|
text: "Failed to save interface!",
|
||||||
@ -302,6 +248,7 @@ async function del() {
|
|||||||
await interfaces.DeleteInterface(selectedInterface.value.Identifier)
|
await interfaces.DeleteInterface(selectedInterface.value.Identifier)
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
notify({
|
notify({
|
||||||
title: "Backend Connection Failure",
|
title: "Backend Connection Failure",
|
||||||
text: "Failed to delete interface!",
|
text: "Failed to delete interface!",
|
||||||
|
@ -5,6 +5,11 @@ import {interfaceStore} from "@/stores/interfaces";
|
|||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
|
import Vue3TagsInput from "vue3-tags-input";
|
||||||
|
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||||
|
import isCidr from "is-cidr";
|
||||||
|
import {isIP} from 'is-ip';
|
||||||
|
import { freshPeer } from '@/helpers/models';
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -52,83 +57,7 @@ const title = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = ref(freshFormData())
|
const formData = ref(freshPeer())
|
||||||
|
|
||||||
|
|
||||||
function freshFormData() {
|
|
||||||
return {
|
|
||||||
Disabled: false,
|
|
||||||
IgnoreGlobalSettings: true,
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
AllowedIPsStr: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
ExtraAllowedIPsStr: "",
|
|
||||||
PrivateKey: "",
|
|
||||||
PublicKey: "",
|
|
||||||
PresharedKey: "",
|
|
||||||
PersistentKeepalive: {
|
|
||||||
Value: 0,
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
DisplayName: "",
|
|
||||||
Identifier: "",
|
|
||||||
UserIdentifier: "",
|
|
||||||
|
|
||||||
InterfaceConfig: {
|
|
||||||
PublicKey: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
AddressStr: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
DnsStr: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
DnsSearchStr: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
Mtu: {
|
|
||||||
Value: 0,
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
FirewallMark: {
|
|
||||||
Value: 0,
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
RoutingTable: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
PreUp: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
PostUp: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
PreDown: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
PostDown: {
|
|
||||||
Value: "",
|
|
||||||
Overridable: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
@ -139,14 +68,72 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
if (!selectedPeer.value) {
|
if (!selectedPeer.value) {
|
||||||
await peers.PreparePeer(selectedInterface.value.Identifier)
|
await peers.PreparePeer(selectedInterface.value.Identifier)
|
||||||
|
|
||||||
formData.value.Disabled = peers.Prepared.Disabled
|
|
||||||
formData.value.Identifier = peers.Prepared.Identifier
|
formData.value.Identifier = peers.Prepared.Identifier
|
||||||
formData.value.DisplayName = peers.Prepared.DisplayName
|
formData.value.DisplayName = peers.Prepared.DisplayName
|
||||||
|
formData.value.UserIdentifier = peers.Prepared.UserIdentifier
|
||||||
|
formData.value.InterfaceIdentifier = peers.Prepared.InterfaceIdentifier
|
||||||
|
formData.value.Disabled = peers.Prepared.Disabled
|
||||||
|
formData.value.ExpiresAt = peers.Prepared.ExpiresAt
|
||||||
|
formData.value.Notes = peers.Prepared.Notes
|
||||||
|
|
||||||
|
formData.value.Endpoint = peers.Prepared.Endpoint
|
||||||
|
formData.value.EndpointPublicKey = peers.Prepared.EndpointPublicKey
|
||||||
|
formData.value.AllowedIPs = peers.Prepared.AllowedIPs
|
||||||
|
formData.value.ExtraAllowedIPs = peers.Prepared.ExtraAllowedIPs
|
||||||
|
formData.value.PresharedKey = peers.Prepared.PresharedKey
|
||||||
|
formData.value.PersistentKeepalive = peers.Prepared.PersistentKeepalive
|
||||||
|
|
||||||
|
formData.value.PrivateKey = peers.Prepared.PrivateKey
|
||||||
|
formData.value.PublicKey = peers.Prepared.PublicKey
|
||||||
|
|
||||||
|
formData.value.Mode = peers.Prepared.Mode
|
||||||
|
|
||||||
|
formData.value.Addresses = peers.Prepared.Addresses
|
||||||
|
formData.value.CheckAliveAddress = peers.Prepared.CheckAliveAddress
|
||||||
|
formData.value.Dns = peers.Prepared.Dns
|
||||||
|
formData.value.DnsSearch = peers.Prepared.DnsSearch
|
||||||
|
formData.value.Mtu = peers.Prepared.Mtu
|
||||||
|
formData.value.FirewallMark = peers.Prepared.FirewallMark
|
||||||
|
formData.value.RoutingTable = peers.Prepared.RoutingTable
|
||||||
|
|
||||||
|
formData.value.PreUp = peers.Prepared.PreUp
|
||||||
|
formData.value.PostUp = peers.Prepared.PostUp
|
||||||
|
formData.value.PreDown = peers.Prepared.PreDown
|
||||||
|
formData.value.PostDown = peers.Prepared.PostDown
|
||||||
|
|
||||||
} else { // fill existing data
|
} else { // fill existing data
|
||||||
formData.value.Disabled = selectedPeer.value.Disabled
|
|
||||||
formData.value.Identifier = selectedPeer.value.Identifier
|
formData.value.Identifier = selectedPeer.value.Identifier
|
||||||
formData.value.DisplayName = selectedPeer.value.DisplayName
|
formData.value.DisplayName = selectedPeer.value.DisplayName
|
||||||
|
formData.value.UserIdentifier = selectedPeer.value.UserIdentifier
|
||||||
|
formData.value.InterfaceIdentifier = selectedPeer.value.InterfaceIdentifier
|
||||||
|
formData.value.Disabled = selectedPeer.value.Disabled
|
||||||
|
formData.value.ExpiresAt = selectedPeer.value.ExpiresAt
|
||||||
|
formData.value.Notes = selectedPeer.value.Notes
|
||||||
|
|
||||||
|
formData.value.Endpoint = selectedPeer.value.Endpoint
|
||||||
|
formData.value.EndpointPublicKey = selectedPeer.value.EndpointPublicKey
|
||||||
|
formData.value.AllowedIPs = selectedPeer.value.AllowedIPs
|
||||||
|
formData.value.ExtraAllowedIPs = selectedPeer.value.ExtraAllowedIPs
|
||||||
|
formData.value.PresharedKey = selectedPeer.value.PresharedKey
|
||||||
|
formData.value.PersistentKeepalive = selectedPeer.value.PersistentKeepalive
|
||||||
|
|
||||||
|
formData.value.PrivateKey = selectedPeer.value.PrivateKey
|
||||||
|
formData.value.PublicKey = selectedPeer.value.PublicKey
|
||||||
|
|
||||||
|
formData.value.Mode = selectedPeer.value.Mode
|
||||||
|
|
||||||
|
formData.value.Addresses = selectedPeer.value.Addresses
|
||||||
|
formData.value.CheckAliveAddress = selectedPeer.value.CheckAliveAddress
|
||||||
|
formData.value.Dns = selectedPeer.value.Dns
|
||||||
|
formData.value.DnsSearch = selectedPeer.value.DnsSearch
|
||||||
|
formData.value.Mtu = selectedPeer.value.Mtu
|
||||||
|
formData.value.FirewallMark = selectedPeer.value.FirewallMark
|
||||||
|
formData.value.RoutingTable = selectedPeer.value.RoutingTable
|
||||||
|
|
||||||
|
formData.value.PreUp = selectedPeer.value.PreUp
|
||||||
|
formData.value.PostUp = selectedPeer.value.PostUp
|
||||||
|
formData.value.PreDown = selectedPeer.value.PreDown
|
||||||
|
formData.value.PostDown = selectedPeer.value.PostDown
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,10 +141,114 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
formData.value = freshFormData()
|
formData.value = freshPeer()
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChangeAddresses(tags) {
|
||||||
|
let validInput = true
|
||||||
|
tags.forEach(tag => {
|
||||||
|
if(isCidr(tag) === 0) {
|
||||||
|
validInput = false
|
||||||
|
notify({
|
||||||
|
title: "Invalid CIDR",
|
||||||
|
text: tag + " is not a valid IP address",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(validInput) {
|
||||||
|
formData.value.Addresses = tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeAllowedIPs(tags) {
|
||||||
|
let validInput = true
|
||||||
|
tags.forEach(tag => {
|
||||||
|
if(isCidr(tag) === 0) {
|
||||||
|
validInput = false
|
||||||
|
notify({
|
||||||
|
title: "Invalid CIDR",
|
||||||
|
text: tag + " is not a valid IP address",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(validInput) {
|
||||||
|
formData.value.AllowedIPs = tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeExtraAllowedIPs(tags) {
|
||||||
|
let validInput = true
|
||||||
|
tags.forEach(tag => {
|
||||||
|
if(isCidr(tag) === 0) {
|
||||||
|
validInput = false
|
||||||
|
notify({
|
||||||
|
title: "Invalid CIDR",
|
||||||
|
text: tag + " is not a valid IP address",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(validInput) {
|
||||||
|
formData.value.ExtraAllowedIPs = tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeDns(tags) {
|
||||||
|
let validInput = true
|
||||||
|
tags.forEach(tag => {
|
||||||
|
if(!isIP(tag)) {
|
||||||
|
validInput = false
|
||||||
|
notify({
|
||||||
|
title: "Invalid IP",
|
||||||
|
text: tag + " is not a valid IP address",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(validInput) {
|
||||||
|
formData.value.Dns = tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeDnsSearch(tags) {
|
||||||
|
formData.value.DnsSearch = tags
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
try {
|
||||||
|
if (props.peerId!=='#NEW#') {
|
||||||
|
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
||||||
|
} else {
|
||||||
|
await peers.CreatePeer(selectedInterface.value.Identifier, formData.value)
|
||||||
|
}
|
||||||
|
close()
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
notify({
|
||||||
|
title: "Backend Connection Failure",
|
||||||
|
text: "Failed to save peer!",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del() {
|
||||||
|
try {
|
||||||
|
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||||
|
close()
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
notify({
|
||||||
|
title: "Backend Connection Failure",
|
||||||
|
text: "Failed to delete peer!",
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -188,6 +279,10 @@ function close() {
|
|||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.presharedkey') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.presharedkey') }}</label>
|
||||||
<input type="email" class="form-control" placeholder="Optional pre-shared key" v-model="formData.PresharedKey">
|
<input type="email" class="form-control" placeholder="Optional pre-shared key" v-model="formData.PresharedKey">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" v-if="formData.Mode==='client'">
|
||||||
|
<label class="form-label mt-4">{{ $t('modals.peeredit.endpointpublickey') }}</label>
|
||||||
|
<input type="text" class="form-control" placeholder="Endpoint Public Key" v-model="formData.EndpointPublicKey.Value">
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="mt-4">Networking</legend>
|
<legend class="mt-4">Networking</legend>
|
||||||
@ -197,19 +292,43 @@ function close() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.ips') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.ips') }}</label>
|
||||||
<input type="text" class="form-control" placeholder="Client IP Address" v-model="formData.InterfaceConfig.AddressStr.Value">
|
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
||||||
|
placeholder="IP Addresses (CIDR format)"
|
||||||
|
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
|
:validate="validateCIDR"
|
||||||
|
@on-tags-changed="handleChangeAddresses"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.allowedips') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.allowedips') }}</label>
|
||||||
<input type="text" class="form-control" placeholder="Allowed IP Address" v-model="formData.AllowedIPsStr.Value">
|
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
||||||
|
placeholder="Allowed IP Addresses (CIDR format)"
|
||||||
|
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
|
:validate="validateCIDR"
|
||||||
|
@on-tags-changed="handleChangeAllowedIPs"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.extraallowedips') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.extraallowedips') }}</label>
|
||||||
<input type="text" class="form-control" placeholder="Extra Allowed IP's (Server Sided)" v-model="formData.ExtraAllowedIPsStr.Value">
|
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
||||||
|
placeholder="Extra allowed IP's (Server Sided)"
|
||||||
|
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
|
:validate="validateCIDR"
|
||||||
|
@on-tags-changed="handleChangeExtraAllowedIPs"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.dns') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.dns') }}</label>
|
||||||
<input type="text" class="form-control" placeholder="Client DNS Servers" v-model="formData.InterfaceConfig.DnsStr.Value">
|
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
||||||
|
placeholder="DNS Servers"
|
||||||
|
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
|
:validate="validateIP"
|
||||||
|
@on-tags-changed="handleChangeDns"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4">{{ $t('modals.peeredit.dnssearch') }}</label>
|
||||||
|
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
||||||
|
placeholder="DNS Search prefixes"
|
||||||
|
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
|
:validate="validateDomain"
|
||||||
|
@on-tags-changed="handleChangeDnsSearch"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
@ -218,7 +337,7 @@ function close() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peeredit.mtu') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peeredit.mtu') }}</label>
|
||||||
<input type="number" class="form-control" placeholder="Client MTU (0 = default)" v-model="formData.InterfaceConfig.Mtu.Value">
|
<input type="number" class="form-control" placeholder="Client MTU (0 = default)" v-model="formData.Mtu.Value">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -228,18 +347,14 @@ function close() {
|
|||||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||||
<label class="form-check-label" >Disabled</label>
|
<label class="form-check-label" >Disabled</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" checked="" v-model="formData.IgnoreGlobalSettings">
|
|
||||||
<label class="form-check-label">Ignore global settings</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button type="button" class="btn btn-danger me-1">Delete</button>
|
<button v-if="props.peerId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary me-1">Save</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save">Save</button>
|
||||||
<button @click.prevent="close" type="button" class="btn btn-secondary">Discard</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">Discard</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
120
frontend/src/helpers/models.js
Normal file
120
frontend/src/helpers/models.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
export function freshInterface() {
|
||||||
|
return {
|
||||||
|
Disabled: false,
|
||||||
|
DisplayName: "",
|
||||||
|
Identifier: "",
|
||||||
|
Mode: "server",
|
||||||
|
|
||||||
|
PublicKey: "",
|
||||||
|
PrivateKey: "",
|
||||||
|
|
||||||
|
ListenPort: 51820,
|
||||||
|
Addresses: [],
|
||||||
|
DnsStr: [],
|
||||||
|
DnsSearch: [],
|
||||||
|
|
||||||
|
Mtu: 0,
|
||||||
|
FirewallMark: 0,
|
||||||
|
RoutingTable: "",
|
||||||
|
|
||||||
|
PreUp: "",
|
||||||
|
PostUp: "",
|
||||||
|
PreDown: "",
|
||||||
|
PostDown: "",
|
||||||
|
|
||||||
|
SaveConfig: false,
|
||||||
|
|
||||||
|
// Peer defaults
|
||||||
|
|
||||||
|
PeerDefNetwork: [],
|
||||||
|
PeerDefDns: [],
|
||||||
|
PeerDefDnsSearch: [],
|
||||||
|
PeerDefEndpoint: "",
|
||||||
|
PeerDefAllowedIPs: [],
|
||||||
|
PeerDefMtu: 0,
|
||||||
|
PeerDefPersistentKeepalive: 0,
|
||||||
|
PeerDefFirewallMark: 0,
|
||||||
|
PeerDefRoutingTable: "",
|
||||||
|
PeerDefPreUp: "",
|
||||||
|
PeerDefPostUp: "",
|
||||||
|
PeerDefPreDown: "",
|
||||||
|
PeerDefPostDown: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function freshPeer() {
|
||||||
|
return {
|
||||||
|
Identifier: "",
|
||||||
|
DisplayName: "",
|
||||||
|
UserIdentifier: "",
|
||||||
|
InterfaceIdentifier: "",
|
||||||
|
Disabled: false,
|
||||||
|
ExpiresAt: null,
|
||||||
|
Notes: "",
|
||||||
|
|
||||||
|
Endpoint: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
EndpointPublicKey: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
AllowedIPs: {
|
||||||
|
Value: [],
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
ExtraAllowedIPs: [],
|
||||||
|
PresharedKey: "",
|
||||||
|
PersistentKeepalive: {
|
||||||
|
Value: 0,
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
PrivateKey: "",
|
||||||
|
PublicKey: "",
|
||||||
|
|
||||||
|
Mode: "client",
|
||||||
|
|
||||||
|
Addresses: [],
|
||||||
|
CheckAliveAddress: "",
|
||||||
|
Dns: {
|
||||||
|
Value: [],
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
DnsSearch: {
|
||||||
|
Value: [],
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
Mtu: {
|
||||||
|
Value: 0,
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
FirewallMark: {
|
||||||
|
Value: 0,
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
RoutingTable: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
PreUp: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
PostUp: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
PreDown: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
},
|
||||||
|
PostDown: {
|
||||||
|
Value: "",
|
||||||
|
Overridable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
frontend/src/helpers/validators.js
Normal file
14
frontend/src/helpers/validators.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import isCidr from "is-cidr";
|
||||||
|
import {isIP} from 'is-ip';
|
||||||
|
|
||||||
|
export function validateCIDR(value) {
|
||||||
|
return isCidr(value) !== 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateIP(value) {
|
||||||
|
return isIP(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateDomain(value) {
|
||||||
|
return true
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
|
|
||||||
import {apiWrapper} from '@/helpers/fetch-wrapper'
|
import {apiWrapper} from '@/helpers/fetch-wrapper'
|
||||||
import {notify} from "@kyvg/vue3-notification";
|
import {notify} from "@kyvg/vue3-notification";
|
||||||
|
import { freshInterface } from '@/helpers/models';
|
||||||
|
|
||||||
const baseUrl = `/interface`
|
const baseUrl = `/interface`
|
||||||
|
|
||||||
@ -9,12 +10,9 @@ export const interfaceStore = defineStore({
|
|||||||
id: 'interfaces',
|
id: 'interfaces',
|
||||||
state: () => ({
|
state: () => ({
|
||||||
interfaces: [],
|
interfaces: [],
|
||||||
prepared: {
|
prepared: freshInterface(),
|
||||||
Identifier: "",
|
|
||||||
Type: "server",
|
|
||||||
},
|
|
||||||
configuration: "",
|
configuration: "",
|
||||||
selected: "wg0",
|
selected: "",
|
||||||
fetching: false,
|
fetching: false,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
@ -30,6 +28,11 @@ export const interfaceStore = defineStore({
|
|||||||
actions: {
|
actions: {
|
||||||
setInterfaces(interfaces) {
|
setInterfaces(interfaces) {
|
||||||
this.interfaces = interfaces
|
this.interfaces = interfaces
|
||||||
|
if (this.interfaces.length > 0) {
|
||||||
|
this.selected = this.interfaces[0].Identifier
|
||||||
|
} else {
|
||||||
|
this.selected = ""
|
||||||
|
}
|
||||||
this.fetching = false
|
this.fetching = false
|
||||||
},
|
},
|
||||||
async LoadInterfaces() {
|
async LoadInterfaces() {
|
||||||
@ -55,7 +58,7 @@ export const interfaceStore = defineStore({
|
|||||||
return apiWrapper.get(`${baseUrl}/prepare`)
|
return apiWrapper.get(`${baseUrl}/prepare`)
|
||||||
.then(this.setPreparedInterface)
|
.then(this.setPreparedInterface)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.prepared = {}
|
this.prepared = freshInterface()
|
||||||
console.log("Failed to load prepared interface: ", error)
|
console.log("Failed to load prepared interface: ", error)
|
||||||
notify({
|
notify({
|
||||||
title: "Backend Connection Failure",
|
title: "Backend Connection Failure",
|
||||||
@ -64,7 +67,7 @@ export const interfaceStore = defineStore({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async InterfaceConfig(id) {
|
async InterfaceConfig(id) {
|
||||||
return apiWrapper.get(`${baseUrl}/config/${id}`)
|
return apiWrapper.get(`${baseUrl}/config/${encodeURIComponent(id)}`)
|
||||||
.then(this.setInterfaceConfig)
|
.then(this.setInterfaceConfig)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.prepared = {}
|
this.prepared = {}
|
||||||
@ -77,9 +80,14 @@ export const interfaceStore = defineStore({
|
|||||||
},
|
},
|
||||||
async DeleteInterface(id) {
|
async DeleteInterface(id) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.delete(`${baseUrl}/${id}`)
|
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.interfaces = this.interfaces.filter(i => i.Identifier !== id)
|
this.interfaces = this.interfaces.filter(i => i.Identifier !== id)
|
||||||
|
if (this.interfaces.length > 0) {
|
||||||
|
this.selected = this.interfaces[0].Identifier
|
||||||
|
} else {
|
||||||
|
this.selected = ""
|
||||||
|
}
|
||||||
this.fetching = false
|
this.fetching = false
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -90,7 +98,7 @@ export const interfaceStore = defineStore({
|
|||||||
},
|
},
|
||||||
async UpdateInterface(id, formData) {
|
async UpdateInterface(id, formData) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.put(`${baseUrl}/${id}`, formData)
|
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData)
|
||||||
.then(iface => {
|
.then(iface => {
|
||||||
let idx = this.interfaces.findIndex((i) => i.Identifier === id)
|
let idx = this.interfaces.findIndex((i) => i.Identifier === id)
|
||||||
this.interfaces[idx] = iface
|
this.interfaces[idx] = iface
|
||||||
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import {apiWrapper} from "../helpers/fetch-wrapper";
|
import {apiWrapper} from "../helpers/fetch-wrapper";
|
||||||
import {notify} from "@kyvg/vue3-notification";
|
import {notify} from "@kyvg/vue3-notification";
|
||||||
import {interfaceStore} from "./interfaces";
|
import {interfaceStore} from "./interfaces";
|
||||||
|
import { freshPeer } from '@/helpers/models';
|
||||||
|
|
||||||
const baseUrl = `/peer`
|
const baseUrl = `/peer`
|
||||||
|
|
||||||
@ -9,9 +10,8 @@ export const peerStore = defineStore({
|
|||||||
id: 'peers',
|
id: 'peers',
|
||||||
state: () => ({
|
state: () => ({
|
||||||
peers: [],
|
peers: [],
|
||||||
prepared: {
|
peer: freshPeer(),
|
||||||
Identifier: "",
|
prepared: freshPeer(),
|
||||||
},
|
|
||||||
filter: "",
|
filter: "",
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
pageOffset: 0,
|
pageOffset: 0,
|
||||||
@ -75,14 +75,18 @@ export const peerStore = defineStore({
|
|||||||
this.calculatePages()
|
this.calculatePages()
|
||||||
this.fetching = false
|
this.fetching = false
|
||||||
},
|
},
|
||||||
|
setPeer(peer) {
|
||||||
|
this.peer = peer
|
||||||
|
this.fetching = false
|
||||||
|
},
|
||||||
setPreparedPeer(peer) {
|
setPreparedPeer(peer) {
|
||||||
this.prepared = peer;
|
this.prepared = peer;
|
||||||
},
|
},
|
||||||
async PreparePeer(interfaceId) {
|
async PreparePeer(interfaceId) {
|
||||||
return apiWrapper.get(`${baseUrl}/iface/${iface.Identifier}/prepare`)
|
return apiWrapper.get(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/prepare`)
|
||||||
.then(this.setPreparedPeer)
|
.then(this.setPreparedPeer)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.prepared = {}
|
this.prepared = freshPeer()
|
||||||
console.log("Failed to load prepared peer: ", error)
|
console.log("Failed to load prepared peer: ", error)
|
||||||
notify({
|
notify({
|
||||||
title: "Backend Connection Failure",
|
title: "Backend Connection Failure",
|
||||||
@ -90,14 +94,70 @@ export const peerStore = defineStore({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async LoadPeers() {
|
async LoadPeer(id) {
|
||||||
let iface = interfaceStore().GetSelected
|
this.fetching = true
|
||||||
if (!iface) {
|
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(id)}`)
|
||||||
return // no interface, nothing to load
|
.then(this.setPeer)
|
||||||
|
.catch(error => {
|
||||||
|
this.setPeers([])
|
||||||
|
console.log("Failed to load peer: ", error)
|
||||||
|
notify({
|
||||||
|
title: "Backend Connection Failure",
|
||||||
|
text: "Failed to load peer!",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async DeletePeer(id) {
|
||||||
|
this.fetching = true
|
||||||
|
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`)
|
||||||
|
.then(() => {
|
||||||
|
this.peers = this.peers.filter(p => p.Identifier !== id)
|
||||||
|
this.fetching = false
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.fetching = false
|
||||||
|
console.log(error)
|
||||||
|
throw new Error(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async UpdatePeer(id, formData) {
|
||||||
|
this.fetching = true
|
||||||
|
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData)
|
||||||
|
.then(peer => {
|
||||||
|
let idx = this.peers.findIndex((p) => p.Identifier === id)
|
||||||
|
this.peers[idx] = peer
|
||||||
|
this.fetching = false
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.fetching = false
|
||||||
|
console.log(error)
|
||||||
|
throw new Error(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async CreatePeer(interfaceId, formData) {
|
||||||
|
this.fetching = true
|
||||||
|
return apiWrapper.post(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/new`, formData)
|
||||||
|
.then(peer => {
|
||||||
|
this.peers.push(peer)
|
||||||
|
this.fetching = false
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.fetching = false
|
||||||
|
console.log(error)
|
||||||
|
throw new Error(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async LoadPeers(interfaceId) {
|
||||||
|
// if no interfaceId is given, use the currently selected interface
|
||||||
|
if (!interfaceId) {
|
||||||
|
interfaceId = interfaceStore().GetSelected.Identifier
|
||||||
|
if (!interfaceId) {
|
||||||
|
return // no interface, nothing to load
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
|
|
||||||
return apiWrapper.get(`${baseUrl}/iface/${iface.Identifier}/all`)
|
return apiWrapper.get(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/all`)
|
||||||
.then(this.setPeers)
|
.then(this.setPeers)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setPeers([])
|
this.setPeers([])
|
||||||
|
@ -79,7 +79,7 @@ export const profileStore = defineStore({
|
|||||||
async LoadPeers() {
|
async LoadPeers() {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
let currentUser = authStore().user.Identifier
|
let currentUser = authStore().user.Identifier
|
||||||
return apiWrapper.get(`${baseUrl}/${currentUser}/peers`)
|
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(currentUser)}/peers`)
|
||||||
.then(this.setPeers)
|
.then(this.setPeers)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setPeers([])
|
this.setPeers([])
|
||||||
@ -93,7 +93,7 @@ export const profileStore = defineStore({
|
|||||||
async LoadUser() {
|
async LoadUser() {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
let currentUser = authStore().user.Identifier
|
let currentUser = authStore().user.Identifier
|
||||||
return apiWrapper.get(`${baseUrl}/${currentUser}`)
|
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(currentUser)}`)
|
||||||
.then(this.setUser)
|
.then(this.setUser)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setUser({})
|
this.setUser({})
|
||||||
|
@ -91,7 +91,7 @@ export const userStore = defineStore({
|
|||||||
},
|
},
|
||||||
async DeleteUser(id) {
|
async DeleteUser(id) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.delete(`${baseUrl}/` + id)
|
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.users = this.users.filter(u => u.Identifier !== id)
|
this.users = this.users.filter(u => u.Identifier !== id)
|
||||||
this.fetching = false
|
this.fetching = false
|
||||||
@ -104,7 +104,7 @@ export const userStore = defineStore({
|
|||||||
},
|
},
|
||||||
async UpdateUser(id, formData) {
|
async UpdateUser(id, formData) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.put(`${baseUrl}/` + id, formData)
|
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData)
|
||||||
.then(user => {
|
.then(user => {
|
||||||
let idx = this.users.findIndex((u) => u.Identifier === id)
|
let idx = this.users.findIndex((u) => u.Identifier === id)
|
||||||
this.users[idx] = user
|
this.users[idx] = user
|
||||||
@ -131,7 +131,7 @@ export const userStore = defineStore({
|
|||||||
},
|
},
|
||||||
async LoadUserPeers(id) {
|
async LoadUserPeers(id) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.get(`${baseUrl}/${id}/peers`)
|
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(id)}/peers`)
|
||||||
.then(this.setUserPeers)
|
.then(this.setUserPeers)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setUserPeers([])
|
this.setUserPeers([])
|
||||||
|
@ -289,7 +289,7 @@ onMounted(async () => {
|
|||||||
<td>
|
<td>
|
||||||
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
|
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint}}</td>
|
<td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint.Value}}</td>
|
||||||
<td>{{peer.LastConnected}}</td>
|
<td>{{peer.LastConnected}}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
|
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"gorm.io/gorm/utils"
|
"gorm.io/gorm/utils"
|
||||||
"os"
|
"os"
|
||||||
@ -317,7 +318,12 @@ func (r *SqlRepo) DeleteInterface(ctx context.Context, id domain.InterfaceIdenti
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.db.WithContext(ctx).Delete(&domain.Interface{}, id).Error
|
err = r.db.WithContext(ctx).Delete(&domain.InterfaceStatus{InterfaceId: id}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.db.WithContext(ctx).Debug().Select(clause.Associations).Delete(&domain.Interface{Identifier: id}).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -359,7 +365,11 @@ func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIden
|
|||||||
func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
||||||
var peer domain.Peer
|
var peer domain.Peer
|
||||||
|
|
||||||
err := r.db.WithContext(ctx).Where("identifier = ?", id).Find(&peer).Error
|
err := r.db.WithContext(ctx).Preload("Addresses").First(&peer, id).Error
|
||||||
|
|
||||||
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -478,7 +488,19 @@ func (r *SqlRepo) upsertPeer(tx *gorm.DB, peer *domain.Peer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
||||||
err := r.db.WithContext(ctx).Delete(&domain.Peer{}, id).Error
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := r.db.WithContext(ctx).Delete(&domain.PeerStatus{PeerId: id}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.db.WithContext(ctx).Select(clause.Associations).Delete(&domain.Peer{Identifier: id}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -486,6 +508,66 @@ func (r *SqlRepo) DeletePeer(ctx context.Context, id domain.PeerIdentifier) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) GetPeerIps(ctx context.Context) (map[domain.PeerIdentifier][]domain.Cidr, error) {
|
||||||
|
var ips []struct {
|
||||||
|
domain.Cidr
|
||||||
|
PeerId domain.PeerIdentifier `gorm:"column:peer_identifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("peer_addresses").
|
||||||
|
Joins("LEFT JOIN cidrs ON peer_addresses.cidr_cidr = cidrs.cidr").
|
||||||
|
Scan(&ips).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[domain.PeerIdentifier][]domain.Cidr)
|
||||||
|
for _, ip := range ips {
|
||||||
|
result[ip.PeerId] = append(result[ip.PeerId], ip.Cidr)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) GetUsedIpsPerSubnet(ctx context.Context) (map[domain.Cidr][]domain.Cidr, error) {
|
||||||
|
var peerIps []struct {
|
||||||
|
domain.Cidr
|
||||||
|
PeerId domain.PeerIdentifier `gorm:"column:peer_identifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("peer_addresses").
|
||||||
|
Joins("LEFT JOIN cidrs ON peer_addresses.cidr_cidr = cidrs.cidr").
|
||||||
|
Scan(&peerIps).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch peer IP's: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interfaceIps []struct {
|
||||||
|
domain.Cidr
|
||||||
|
InterfaceId domain.InterfaceIdentifier `gorm:"column:interface_identifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.db.WithContext(ctx).
|
||||||
|
Table("interface_addresses").
|
||||||
|
Joins("LEFT JOIN cidrs ON interface_addresses.cidr_cidr = cidrs.cidr").
|
||||||
|
Scan(&interfaceIps).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch interface IP's: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[domain.Cidr][]domain.Cidr)
|
||||||
|
for _, ip := range interfaceIps {
|
||||||
|
networkAddr := ip.Cidr.NetworkAddr()
|
||||||
|
result[networkAddr] = append(result[networkAddr], ip.Cidr)
|
||||||
|
}
|
||||||
|
for _, ip := range peerIps {
|
||||||
|
networkAddr := ip.Cidr.NetworkAddr()
|
||||||
|
result[networkAddr] = append(result[networkAddr], ip.Cidr)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// endregion peers
|
// endregion peers
|
||||||
|
|
||||||
// region users
|
// region users
|
||||||
|
@ -194,12 +194,12 @@ func (e peerEndpoint) handleUpdatePut() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.InterfaceIdentifier != peerId {
|
if p.Identifier != peerId {
|
||||||
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "peer id mismatch"})
|
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "peer id mismatch"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedPeer, err := e.app.UpdateInterface(ctx, model.NewDomainPeer(&p))
|
updatedPeer, err := e.app.UpdatePeer(ctx, model.NewDomainPeer(&p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||||
return
|
return
|
||||||
@ -232,9 +232,7 @@ func (e peerEndpoint) handleDelete() gin.HandlerFunc {
|
|||||||
|
|
||||||
err := e.app.DeletePeer(ctx, domain.PeerIdentifier(id))
|
err := e.app.DeletePeer(ctx, domain.PeerIdentifier(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, model.Error{
|
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||||
Code: http.StatusInternalServerError, Message: err.Error(),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StringConfigOption struct {
|
type StringConfigOption struct {
|
||||||
Value string `json:"value"`
|
Value string `json:"Value"`
|
||||||
Overridable bool `json:"overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStringConfigOption(value string, overridable bool) StringConfigOption {
|
func NewStringConfigOption(value string, overridable bool) StringConfigOption {
|
||||||
@ -32,8 +32,8 @@ func StringConfigOptionToDomain(opt StringConfigOption) domain.StringConfigOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StringSliceConfigOption struct {
|
type StringSliceConfigOption struct {
|
||||||
Value []string `json:"value"`
|
Value []string `json:"Value"`
|
||||||
Overridable bool `json:"overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStringSliceConfigOption(value []string, overridable bool) StringSliceConfigOption {
|
func NewStringSliceConfigOption(value []string, overridable bool) StringSliceConfigOption {
|
||||||
@ -58,8 +58,8 @@ func StringSliceConfigOptionToDomain(opt StringSliceConfigOption) domain.StringC
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IntConfigOption struct {
|
type IntConfigOption struct {
|
||||||
Value int `json:"value"`
|
Value int `json:"Value"`
|
||||||
Overridable bool `json:"overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIntConfigOption(value int, overridable bool) IntConfigOption {
|
func NewIntConfigOption(value int, overridable bool) IntConfigOption {
|
||||||
@ -84,8 +84,8 @@ func IntConfigOptionToDomain(opt IntConfigOption) domain.IntConfigOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Int32ConfigOption struct {
|
type Int32ConfigOption struct {
|
||||||
Value int32 `json:"value"`
|
Value int32 `json:"Value"`
|
||||||
Overridable bool `json:"overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInt32ConfigOption(value int32, overridable bool) Int32ConfigOption {
|
func NewInt32ConfigOption(value int32, overridable bool) Int32ConfigOption {
|
||||||
@ -110,8 +110,8 @@ func Int32ConfigOptionToDomain(opt Int32ConfigOption) domain.Int32ConfigOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BoolConfigOption struct {
|
type BoolConfigOption struct {
|
||||||
Value bool `json:"value"`
|
Value bool `json:"Value"`
|
||||||
Overridable bool `json:"overridable"`
|
Overridable bool `json:"Overridable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoolConfigOption(value bool, overridable bool) BoolConfigOption {
|
func NewBoolConfigOption(value bool, overridable bool) BoolConfigOption {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"Code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"Message"`
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,11 @@ type Peer struct {
|
|||||||
InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id
|
InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id
|
||||||
Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down)
|
Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down)
|
||||||
DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled
|
DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled
|
||||||
ExpiresAt *time.Time `json:"column:expires_at"` // expiry dates for peers
|
ExpiresAt *time.Time `json:"ExpiresAt"` // expiry dates for peers
|
||||||
Notes string `json:"notes"` // a note field for peers
|
Notes string `json:"Notes"` // a note field for peers
|
||||||
|
|
||||||
Endpoint StringConfigOption `json:"Endpoint"` // the endpoint address
|
Endpoint StringConfigOption `json:"Endpoint"` // the endpoint address
|
||||||
EndpointPublicKey string `json:"EndpointPublicKey"` // the endpoint public key
|
EndpointPublicKey StringConfigOption `json:"EndpointPublicKey"` // the endpoint public key
|
||||||
AllowedIPs StringSliceConfigOption `json:"AllowedIPs"` // all allowed ip subnets, comma seperated
|
AllowedIPs StringSliceConfigOption `json:"AllowedIPs"` // all allowed ip subnets, comma seperated
|
||||||
ExtraAllowedIPs []string `json:"ExtraAllowedIPs"` // all allowed ip subnets on the server side, comma seperated
|
ExtraAllowedIPs []string `json:"ExtraAllowedIPs"` // all allowed ip subnets on the server side, comma seperated
|
||||||
PresharedKey string `json:"PresharedKey"` // the pre-shared Key of the peer
|
PresharedKey string `json:"PresharedKey"` // the pre-shared Key of the peer
|
||||||
@ -53,7 +53,7 @@ func NewPeer(src *domain.Peer) *Peer {
|
|||||||
ExpiresAt: src.ExpiresAt,
|
ExpiresAt: src.ExpiresAt,
|
||||||
Notes: src.Notes,
|
Notes: src.Notes,
|
||||||
Endpoint: StringConfigOptionFromDomain(src.Endpoint),
|
Endpoint: StringConfigOptionFromDomain(src.Endpoint),
|
||||||
EndpointPublicKey: src.EndpointPublicKey,
|
EndpointPublicKey: StringConfigOptionFromDomain(src.EndpointPublicKey),
|
||||||
AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr),
|
AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr),
|
||||||
ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr),
|
ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr),
|
||||||
PresharedKey: string(src.PresharedKey),
|
PresharedKey: string(src.PresharedKey),
|
||||||
@ -92,7 +92,7 @@ func NewDomainPeer(src *Peer) *domain.Peer {
|
|||||||
res := &domain.Peer{
|
res := &domain.Peer{
|
||||||
BaseModel: domain.BaseModel{},
|
BaseModel: domain.BaseModel{},
|
||||||
Endpoint: StringConfigOptionToDomain(src.Endpoint),
|
Endpoint: StringConfigOptionToDomain(src.Endpoint),
|
||||||
EndpointPublicKey: src.EndpointPublicKey,
|
EndpointPublicKey: StringConfigOptionToDomain(src.EndpointPublicKey),
|
||||||
AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs),
|
AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs),
|
||||||
ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs),
|
ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs),
|
||||||
PresharedKey: domain.PreSharedKey(src.PresharedKey),
|
PresharedKey: domain.PreSharedKey(src.PresharedKey),
|
||||||
|
@ -320,7 +320,9 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
|||||||
Endpoint: domain.StringConfigOption{
|
Endpoint: domain.StringConfigOption{
|
||||||
Value: oldPeer.Endpoint, Overridable: !oldPeer.IgnoreGlobalSettings,
|
Value: oldPeer.Endpoint, Overridable: !oldPeer.IgnoreGlobalSettings,
|
||||||
},
|
},
|
||||||
EndpointPublicKey: iface.PublicKey,
|
EndpointPublicKey: domain.StringConfigOption{
|
||||||
|
Value: iface.PublicKey, Overridable: !oldPeer.IgnoreGlobalSettings,
|
||||||
|
},
|
||||||
AllowedIPsStr: domain.StringConfigOption{
|
AllowedIPsStr: domain.StringConfigOption{
|
||||||
Value: oldPeer.AllowedIPsStr, Overridable: !oldPeer.IgnoreGlobalSettings,
|
Value: oldPeer.AllowedIPsStr, Overridable: !oldPeer.IgnoreGlobalSettings,
|
||||||
},
|
},
|
||||||
@ -333,7 +335,6 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
|||||||
Identifier: domain.PeerIdentifier(oldPeer.PublicKey),
|
Identifier: domain.PeerIdentifier(oldPeer.PublicKey),
|
||||||
UserIdentifier: user.Identifier,
|
UserIdentifier: user.Identifier,
|
||||||
InterfaceIdentifier: iface.Identifier,
|
InterfaceIdentifier: iface.Identifier,
|
||||||
Temporary: nil,
|
|
||||||
Disabled: disableTime,
|
Disabled: disableTime,
|
||||||
DisabledReason: disableReason,
|
DisabledReason: disableReason,
|
||||||
ExpiresAt: expiryTime,
|
ExpiresAt: expiryTime,
|
||||||
|
@ -38,8 +38,10 @@ type WireGuardManager interface {
|
|||||||
UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error)
|
UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error)
|
||||||
DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error
|
DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error
|
||||||
PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error)
|
PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error)
|
||||||
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
|
||||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||||
|
CreatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error)
|
||||||
|
UpdatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error)
|
||||||
|
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatisticsCollector interface {
|
type StatisticsCollector interface {
|
||||||
|
@ -20,6 +20,7 @@ type InterfaceAndPeerDatabaseRepo interface {
|
|||||||
SavePeer(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.Peer) (*domain.Peer, error)) error
|
SavePeer(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.Peer) (*domain.Peer, error)) error
|
||||||
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
||||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||||
|
GetUsedIpsPerSubnet(ctx context.Context) (map[domain.Cidr][]domain.Cidr, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatisticsDatabaseRepo interface {
|
type StatisticsDatabaseRepo interface {
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/h44z/wg-portal/internal/app"
|
"github.com/h44z/wg-portal/internal/app"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -154,7 +153,7 @@ func (m Manager) importPeer(ctx context.Context, in *domain.Interface, p *domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer.InterfaceIdentifier = in.Identifier
|
peer.InterfaceIdentifier = in.Identifier
|
||||||
peer.EndpointPublicKey = in.PublicKey
|
peer.EndpointPublicKey = domain.StringConfigOption{Value: in.PublicKey, Overridable: true}
|
||||||
peer.AllowedIPsStr = domain.StringConfigOption{Value: in.PeerDefAllowedIPsStr, Overridable: true}
|
peer.AllowedIPsStr = domain.StringConfigOption{Value: in.PeerDefAllowedIPsStr, Overridable: true}
|
||||||
peer.Interface.Addresses = p.AllowedIPs // use allowed IP's as the peer IP's
|
peer.Interface.Addresses = p.AllowedIPs // use allowed IP's as the peer IP's
|
||||||
peer.Interface.DnsStr = domain.StringConfigOption{Value: in.PeerDefDnsStr, Overridable: true}
|
peer.Interface.DnsStr = domain.StringConfigOption{Value: in.PeerDefDnsStr, Overridable: true}
|
||||||
@ -298,7 +297,7 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error
|
|||||||
return nil, fmt.Errorf("failed to generate new identifier: %w", err)
|
return nil, fmt.Errorf("failed to generate new identifier: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv4, ipv6, err := m.getFreshIpConfig(ctx)
|
ipv4, ipv6, err := m.getFreshInterfaceIpConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate new ip config: %w", err)
|
return nil, fmt.Errorf("failed to generate new ip config: %w", err)
|
||||||
}
|
}
|
||||||
@ -390,7 +389,7 @@ func (m Manager) getNewInterfaceName(ctx context.Context) (domain.InterfaceIdent
|
|||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) getFreshIpConfig(ctx context.Context) (ipV4, ipV6 domain.Cidr, err error) {
|
func (m Manager) getFreshInterfaceIpConfig(ctx context.Context) (ipV4, ipV6 domain.Cidr, err error) {
|
||||||
ips, err := m.db.GetInterfaceIps(ctx)
|
ips, err := m.db.GetInterfaceIps(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to get existing IP addresses: %w", err)
|
err = fmt.Errorf("failed to get existing IP addresses: %w", err)
|
||||||
@ -401,34 +400,49 @@ func (m Manager) getFreshIpConfig(ctx context.Context) (ipV4, ipV6 domain.Cidr,
|
|||||||
ipV4, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV4)
|
ipV4, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV4)
|
||||||
ipV6, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV6)
|
ipV6, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV6)
|
||||||
|
|
||||||
|
netV4 := ipV4.NetworkAddr()
|
||||||
|
netV6 := ipV6.NetworkAddr()
|
||||||
for {
|
for {
|
||||||
ipV4Conflict := false
|
v4Conflict := false
|
||||||
ipV6Conflict := false
|
v6Conflict := false
|
||||||
for _, usedIps := range ips {
|
for _, usedIps := range ips {
|
||||||
for _, ip := range usedIps {
|
for _, usedIp := range usedIps {
|
||||||
if ipV4 == ip {
|
usedNetwork := usedIp.NetworkAddr()
|
||||||
ipV4Conflict = true
|
if netV4 == usedNetwork {
|
||||||
|
v4Conflict = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipV6 == ip {
|
if netV6 == usedNetwork {
|
||||||
ipV6Conflict = true
|
v6Conflict = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ipV4Conflict && (!useV6 || !ipV6Conflict) {
|
if !v4Conflict && (!useV6 || !v6Conflict) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipV4Conflict {
|
if v4Conflict {
|
||||||
ipV4 = ipV4.NextSubnet()
|
netV4 = netV4.NextSubnet()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipV6Conflict && useV6 {
|
if v6Conflict && useV6 {
|
||||||
ipV6 = ipV6.NextSubnet()
|
netV6 = netV6.NextSubnet()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !netV4.IsValid() {
|
||||||
|
return domain.Cidr{}, domain.Cidr{}, fmt.Errorf("IPv4 space exhausted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if useV6 && !netV6.IsValid() {
|
||||||
|
return domain.Cidr{}, domain.Cidr{}, fmt.Errorf("IPv6 space exhausted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use first address in network for interface
|
||||||
|
ipV4 = netV4.NextAddr()
|
||||||
|
ipV6 = netV6.NextAddr()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +485,7 @@ func (m Manager) CreateInterface(ctx context.Context, in *domain.Interface) (*do
|
|||||||
return nil, fmt.Errorf("interface %s already exists", in.Identifier)
|
return nil, fmt.Errorf("interface %s already exists", in.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.validateCreation(ctx, existingInterface, in); err != nil {
|
if err := m.validateInterfaceCreation(ctx, existingInterface, in); err != nil {
|
||||||
return nil, fmt.Errorf("creation not allowed: %w", err)
|
return nil, fmt.Errorf("creation not allowed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,7 +515,7 @@ func (m Manager) UpdateInterface(ctx context.Context, in *domain.Interface) (*do
|
|||||||
return nil, fmt.Errorf("unable to load existing interface %s: %w", in.Identifier, err)
|
return nil, fmt.Errorf("unable to load existing interface %s: %w", in.Identifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.validateModifications(ctx, existingInterface, in); err != nil {
|
if err := m.validateInterfaceModifications(ctx, existingInterface, in); err != nil {
|
||||||
return nil, fmt.Errorf("update not allowed: %w", err)
|
return nil, fmt.Errorf("update not allowed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +545,7 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
|
|||||||
return fmt.Errorf("unable to find interface %s: %w", id, err)
|
return fmt.Errorf("unable to find interface %s: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.validateDeletion(ctx, existingInterface); err != nil {
|
if err := m.validateInterfaceDeletion(ctx, existingInterface); err != nil {
|
||||||
return fmt.Errorf("deletion not allowed: %w", err)
|
return fmt.Errorf("deletion not allowed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +567,7 @@ func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentif
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateModifications(ctx context.Context, old, new *domain.Interface) error {
|
func (m Manager) validateInterfaceModifications(ctx context.Context, old, new *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin {
|
if !currentUser.IsAdmin {
|
||||||
@ -563,7 +577,7 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Int
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateCreation(ctx context.Context, old, new *domain.Interface) error {
|
func (m Manager) validateInterfaceCreation(ctx context.Context, old, new *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if new.Identifier == "" {
|
if new.Identifier == "" {
|
||||||
@ -577,7 +591,7 @@ func (m Manager) validateCreation(ctx context.Context, old, new *domain.Interfac
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) validateDeletion(ctx context.Context, del *domain.Interface) error {
|
func (m Manager) validateInterfaceDeletion(ctx context.Context, del *domain.Interface) error {
|
||||||
currentUser := domain.GetUserInfo(ctx)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
if !currentUser.IsAdmin {
|
if !currentUser.IsAdmin {
|
||||||
@ -615,6 +629,11 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
|||||||
return nil, fmt.Errorf("unable to find interface %s: %w", id, err)
|
return nil, fmt.Errorf("unable to find interface %s: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ips, err := m.getFreshPeerIpConfig(ctx, iface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get fresh ip addresses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
kp, err := domain.NewFreshKeypair()
|
kp, err := domain.NewFreshKeypair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate keys: %w", err)
|
return nil, fmt.Errorf("failed to generate keys: %w", err)
|
||||||
@ -630,7 +649,7 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
|||||||
peerMode = domain.InterfaceTypeServer
|
peerMode = domain.InterfaceTypeServer
|
||||||
}
|
}
|
||||||
|
|
||||||
peerId := domain.PeerIdentifier(uuid.New().String())
|
peerId := domain.PeerIdentifier(kp.PublicKey)
|
||||||
freshPeer := &domain.Peer{
|
freshPeer := &domain.Peer{
|
||||||
BaseModel: domain.BaseModel{
|
BaseModel: domain.BaseModel{
|
||||||
CreatedBy: string(currentUser.Id),
|
CreatedBy: string(currentUser.Id),
|
||||||
@ -639,7 +658,7 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
|||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
Endpoint: domain.NewStringConfigOption(iface.PeerDefEndpoint, true),
|
Endpoint: domain.NewStringConfigOption(iface.PeerDefEndpoint, true),
|
||||||
EndpointPublicKey: iface.PublicKey,
|
EndpointPublicKey: domain.NewStringConfigOption(iface.PublicKey, true),
|
||||||
AllowedIPsStr: domain.NewStringConfigOption(iface.PeerDefAllowedIPsStr, true),
|
AllowedIPsStr: domain.NewStringConfigOption(iface.PeerDefAllowedIPsStr, true),
|
||||||
ExtraAllowedIPsStr: "",
|
ExtraAllowedIPsStr: "",
|
||||||
PresharedKey: pk,
|
PresharedKey: pk,
|
||||||
@ -655,7 +674,7 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
|||||||
Interface: domain.PeerInterfaceConfig{
|
Interface: domain.PeerInterfaceConfig{
|
||||||
KeyPair: kp,
|
KeyPair: kp,
|
||||||
Type: peerMode,
|
Type: peerMode,
|
||||||
Addresses: nil, // TODO
|
Addresses: ips,
|
||||||
CheckAliveAddress: "",
|
CheckAliveAddress: "",
|
||||||
DnsStr: domain.NewStringConfigOption(iface.PeerDefDnsStr, true),
|
DnsStr: domain.NewStringConfigOption(iface.PeerDefDnsStr, true),
|
||||||
DnsSearchStr: domain.NewStringConfigOption(iface.PeerDefDnsSearchStr, true),
|
DnsSearchStr: domain.NewStringConfigOption(iface.PeerDefDnsSearchStr, true),
|
||||||
@ -672,6 +691,121 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
|
|||||||
return freshPeer, nil
|
return freshPeer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Manager) getFreshPeerIpConfig(ctx context.Context, iface *domain.Interface) (ips []domain.Cidr, err error) {
|
||||||
|
networks, err := domain.CidrsFromString(iface.PeerDefNetworkStr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to parse default network address: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existingIps, err := m.db.GetUsedIpsPerSubnet(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get existing IP addresses: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range networks {
|
||||||
|
ip := network.NextAddr()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ipConflict := false
|
||||||
|
for _, usedIp := range existingIps[network] {
|
||||||
|
if usedIp == ip {
|
||||||
|
ipConflict = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ipConflict {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ip.NextAddr()
|
||||||
|
|
||||||
|
if !ip.IsValid() {
|
||||||
|
return nil, fmt.Errorf("ip space on subnet %s is exhausted", network.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Manager) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
||||||
|
peer, err := m.db.GetPeer(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find peer %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) {
|
||||||
|
existingPeer, err := m.db.GetPeer(ctx, peer.Identifier)
|
||||||
|
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
||||||
|
return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err)
|
||||||
|
}
|
||||||
|
if existingPeer != nil {
|
||||||
|
return nil, fmt.Errorf("peer %s already exists", peer.Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.validatePeerCreation(ctx, existingPeer, peer); err != nil {
|
||||||
|
return nil, fmt.Errorf("creation not allowed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
|
||||||
|
peer.CopyCalculatedAttributes(p)
|
||||||
|
|
||||||
|
err = m.wg.SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
|
||||||
|
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
|
||||||
|
domain.MergeToPhysicalPeer(pp, peer)
|
||||||
|
return pp, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create wireguard peer %s: %w", peer.Identifier, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creation failure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) {
|
||||||
|
existingPeer, err := m.db.GetPeer(ctx, peer.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.validatePeerModifications(ctx, existingPeer, peer); err != nil {
|
||||||
|
return nil, fmt.Errorf("update not allowed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
|
||||||
|
peer.CopyCalculatedAttributes(p)
|
||||||
|
|
||||||
|
err = m.wg.SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
|
||||||
|
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
|
||||||
|
domain.MergeToPhysicalPeer(pp, peer)
|
||||||
|
return pp, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wireguard peer %s: %w", peer.Identifier, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("update failure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m Manager) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
func (m Manager) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
||||||
peer, err := m.db.GetPeer(ctx, id)
|
peer, err := m.db.GetPeer(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -691,11 +825,36 @@ func (m Manager) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain.Peer) error {
|
||||||
peer, err := m.db.GetPeer(ctx, id)
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to find peer %s: %w", id, err)
|
if !currentUser.IsAdmin {
|
||||||
|
return fmt.Errorf("insufficient permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
return peer, nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer) error {
|
||||||
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
|
if new.Identifier == "" {
|
||||||
|
return fmt.Errorf("invalid peer identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !currentUser.IsAdmin {
|
||||||
|
return fmt.Errorf("insufficient permissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) error {
|
||||||
|
currentUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
|
if !currentUser.IsAdmin {
|
||||||
|
return fmt.Errorf("insufficient permissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ func (c Cidr) String() string {
|
|||||||
return c.Prefix().String()
|
return c.Prefix().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Cidr) IsValid() bool {
|
||||||
|
return c.Prefix().IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
func CidrFromString(str string) (Cidr, error) {
|
func CidrFromString(str string) (Cidr, error) {
|
||||||
prefix, err := netip.ParsePrefix(strings.TrimSpace(str))
|
prefix, err := netip.ParsePrefix(strings.TrimSpace(str))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,16 +145,20 @@ func (c Cidr) NetworkAddr() Cidr {
|
|||||||
|
|
||||||
func (c Cidr) NextAddr() Cidr {
|
func (c Cidr) NextAddr() Cidr {
|
||||||
prefix := c.Prefix()
|
prefix := c.Prefix()
|
||||||
|
nextAddr := prefix.Addr().Next()
|
||||||
return Cidr{
|
return Cidr{
|
||||||
Addr: prefix.Addr().Next().String(),
|
Cidr: netip.PrefixFrom(nextAddr, c.NetLength).String(),
|
||||||
|
Addr: nextAddr.String(),
|
||||||
NetLength: prefix.Bits(),
|
NetLength: prefix.Bits(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Cidr) NextSubnet() Cidr {
|
func (c Cidr) NextSubnet() Cidr {
|
||||||
prefix := c.Prefix()
|
prefix := c.Prefix()
|
||||||
|
nextAddr := c.BroadcastAddr().Prefix().Addr().Next()
|
||||||
return Cidr{
|
return Cidr{
|
||||||
Addr: c.BroadcastAddr().Prefix().Addr().Next().String(),
|
Cidr: netip.PrefixFrom(nextAddr, c.NetLength).String(),
|
||||||
|
Addr: nextAddr.String(),
|
||||||
NetLength: prefix.Bits(),
|
NetLength: prefix.Bits(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ type Peer struct {
|
|||||||
// WireGuard specific (for the [peer] section of the config file)
|
// WireGuard specific (for the [peer] section of the config file)
|
||||||
|
|
||||||
Endpoint StringConfigOption `gorm:"embedded;embeddedPrefix:endpoint_"` // the endpoint address
|
Endpoint StringConfigOption `gorm:"embedded;embeddedPrefix:endpoint_"` // the endpoint address
|
||||||
EndpointPublicKey string `gorm:"column:endpoint_pubkey"` // the endpoint public key
|
EndpointPublicKey StringConfigOption `gorm:"embedded;embeddedPrefix:endpoint_pubkey_"` // the endpoint public key
|
||||||
AllowedIPsStr StringConfigOption `gorm:"embedded;embeddedPrefix:allowed_ips_str_"` // all allowed ip subnets, comma seperated
|
AllowedIPsStr StringConfigOption `gorm:"embedded;embeddedPrefix:allowed_ips_str_"` // all allowed ip subnets, comma seperated
|
||||||
ExtraAllowedIPsStr string // all allowed ip subnets on the server side, comma seperated
|
ExtraAllowedIPsStr string // all allowed ip subnets on the server side, comma seperated
|
||||||
PresharedKey PreSharedKey // the pre-shared Key of the peer
|
PresharedKey PreSharedKey // the pre-shared Key of the peer
|
||||||
@ -49,11 +49,11 @@ type Peer struct {
|
|||||||
Interface PeerInterfaceConfig `gorm:"embedded"`
|
Interface PeerInterfaceConfig `gorm:"embedded"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Peer) IsDisabled() bool {
|
func (p *Peer) IsDisabled() bool {
|
||||||
return p.Disabled != nil
|
return p.Disabled != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Peer) CheckAliveAddress() string {
|
func (p *Peer) CheckAliveAddress() string {
|
||||||
if p.Interface.CheckAliveAddress != "" {
|
if p.Interface.CheckAliveAddress != "" {
|
||||||
return p.Interface.CheckAliveAddress
|
return p.Interface.CheckAliveAddress
|
||||||
}
|
}
|
||||||
@ -65,6 +65,10 @@ func (p Peer) CheckAliveAddress() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Peer) CopyCalculatedAttributes(src *Peer) {
|
||||||
|
p.BaseModel = src.BaseModel
|
||||||
|
}
|
||||||
|
|
||||||
type PeerInterfaceConfig struct {
|
type PeerInterfaceConfig struct {
|
||||||
KeyPair // private/public Key of the peer
|
KeyPair // private/public Key of the peer
|
||||||
|
|
||||||
@ -149,7 +153,7 @@ func (p PhysicalPeer) GetAllowedIPs() ([]net.IPNet, error) {
|
|||||||
func ConvertPhysicalPeer(pp *PhysicalPeer) *Peer {
|
func ConvertPhysicalPeer(pp *PhysicalPeer) *Peer {
|
||||||
peer := &Peer{
|
peer := &Peer{
|
||||||
Endpoint: StringConfigOption{Value: pp.Endpoint, Overridable: true},
|
Endpoint: StringConfigOption{Value: pp.Endpoint, Overridable: true},
|
||||||
EndpointPublicKey: "",
|
EndpointPublicKey: StringConfigOption{Value: "", Overridable: true},
|
||||||
AllowedIPsStr: StringConfigOption{Value: "", Overridable: true},
|
AllowedIPsStr: StringConfigOption{Value: "", Overridable: true},
|
||||||
ExtraAllowedIPsStr: "",
|
ExtraAllowedIPsStr: "",
|
||||||
PresharedKey: pp.PresharedKey,
|
PresharedKey: pp.PresharedKey,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user