+
{{ t('standalone.openvpn_rw.certificate_expired_tooltip') }}
-
+
-
diff --git a/src/components/standalone/openvpn_rw/RWServerDetails.vue b/src/components/standalone/openvpn_rw/RWServerDetails.vue
index 336520ec9..ff0223180 100644
--- a/src/components/standalone/openvpn_rw/RWServerDetails.vue
+++ b/src/components/standalone/openvpn_rw/RWServerDetails.vue
@@ -1,24 +1,56 @@
@@ -35,17 +67,19 @@ const emit = defineEmits(['delete-server', 'edit-server'])
-
+
{{ t('standalone.openvpn_rw.status') }}
-
@@ -54,10 +88,6 @@ const emit = defineEmits(['delete-server', 'edit-server'])
-
-
{{ t('standalone.openvpn_rw.authentication_mode') }}
-
{{ t(`standalone.openvpn_rw.${server.ns_auth_mode}`) }}
-
{{ t('standalone.openvpn_rw.database') }}
@@ -68,21 +98,155 @@ const emit = defineEmits(['delete-server', 'edit-server'])
}}
+
+
{{ t('standalone.openvpn_rw.authentication_mode') }}
+
{{ t(`standalone.openvpn_rw.${server.ns_auth_mode}`) }}
+
+
+
+ {{ t('standalone.openvpn_rw.certificates_expiration') }}
+
+
+
+
+ {{ t('standalone.openvpn_rw.server') }}
+ {{
+ server.certificates.server
+ ? new Date(server.certificates.server * 1000).toLocaleString(locale)
+ : '-'
+ }}
+
+
+
+
+
+
+
+
+
+ {{
+ t('standalone.openvpn_rw.certificate_expiring_tooltip', {
+ days: getDaysUntilExpiry(server.certificates.server)
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('standalone.openvpn_rw.certificate_expired_tooltip') }}
+
+
+
+
+
+
+
+ {{ t('standalone.openvpn_rw.ca') }}
+ {{
+ server.certificates.CA
+ ? new Date(server.certificates.CA * 1000).toLocaleString(locale)
+ : '-'
+ }}
+
+
+
+
+
+
+
+
+
+ {{
+ t('standalone.openvpn_rw.certificate_expiring_tooltip', {
+ days: getDaysUntilExpiry(server.certificates.CA)
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('standalone.openvpn_rw.certificate_expired_tooltip') }}
+
+
+
+
+
-
-
-
-
- {{ t('common.edit') }}
-
+import { ubusCall } from '@/lib/standalone/ubus'
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { getAxiosErrorMessage, NeTextInput } from '@nethesis/vue-components'
+import { NeModal, NeInlineNotification } from '@nethesis/vue-components'
+
+const props = defineProps<{
+ visible: boolean
+ instanceName?: string
+ serverName?: string
+}>()
+
+const emit = defineEmits(['close', 'all-certificates-regenerated'])
+
+const { t } = useI18n()
+
+const error = ref()
+const serverNameModal = ref('')
+const isRegenerating = ref(false)
+
+async function regenerateAllCertificates() {
+ if (props.instanceName) {
+ try {
+ error.value = undefined
+ isRegenerating.value = true
+ await ubusCall('ns.ovpnrw', 'regenerate-all-certificates', {
+ instance: props.instanceName
+ })
+ emit('all-certificates-regenerated')
+ emit('close')
+ } catch (err: any) {
+ error.value = err
+ } finally {
+ isRegenerating.value = false
+ }
+ }
+}
+
+function close() {
+ if (!isRegenerating.value) {
+ error.value = undefined
+ serverNameModal.value = ''
+ emit('close')
+ }
+}
+
+
+
+
+
+ {{ t('standalone.openvpn_rw.regenerate_all_certificates_message') }}
+
+
+
diff --git a/src/components/standalone/openvpn_rw/RenewRWServerCertificateModal.vue b/src/components/standalone/openvpn_rw/RenewRWServerCertificateModal.vue
new file mode 100644
index 000000000..1cd0ef88a
--- /dev/null
+++ b/src/components/standalone/openvpn_rw/RenewRWServerCertificateModal.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ {{ t('standalone.openvpn_rw.renew_server_certificate_message') }}
+
+
diff --git a/src/i18n/en.json b/src/i18n/en.json
index a3f12a941..4ebbf8c11 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -427,7 +427,9 @@
"cannot_reuse_old_password": "New password must be different from the old one",
"duplicate_ip": "IP has already been used",
"duplicate_port": "Port has already been used",
- "cannot_regenerate_cert": "Cannot regenerate certificates"
+ "cannot_regenerate_cert": "Cannot regenerate certificates",
+ "cannot_renew_server_cert": "Cannot renew server certificate",
+ "cannot_regenerate_all_certs": "Cannot regenerate all certificates"
},
"ne_text_input": {
"show_password": "Show password",
@@ -1914,7 +1916,7 @@
"status": "Status",
"connected_clients": "Connected clients",
"roadwarrior_accounts": "Road Warrior accounts",
- "roadwarrior_accounts_description": "Users in this list can connect to the OpenVPN Road Warrior server. Only users with a configured password can use the 'username and password' authentication mode. If the selected authentication mode requires an x509 certificate, the certificate must be valid. Changes to users take effect immediately.",
+ "roadwarrior_accounts_description": "Users in this list can connect to the OpenVPN Road Warrior server. Depending on the authentication mode, a password and/or a valid x509 certificate is required. Changes take effect immediately.",
"username_password": "Username and password",
"certificate": "Certificate",
"username_password_certificate": "Username, password and certificate",
@@ -2003,7 +2005,23 @@
"filter_accounts": "Filter accounts",
"cannot_download_history": "Cannot download history",
"cannot_fetch_history": "Cannot fetch history"
- }
+ },
+ "certificates_expiration": "Certificates expiration",
+ "certificate_expiration": "Certificate expiration",
+ "certificate_expiring_tooltip": "The certificate will expire in {days} days",
+ "certificate_expired_tooltip": "The certificate is expired",
+ "renew_server_certificate": "Renew server certificate",
+ "regenerate_all_certificates": "Regenerate all certificates",
+ "renew_server_certificate_message": "This action will renew the server certificate using the existing Certification Authority (CA). Client configurations and certificates will remain valid.",
+ "regenerate_all_certificates_message": "This action will regenerate the Certification Authority (CA) and all server certificates. All existing certificates will become invalid and clients will need to download and install new configuration files.",
+ "server_certificate_renewed": "Server certificate renewed",
+ "server_certificate_renewed_message": "The server certificate has been successfully renewed.",
+ "all_certificates_regenerated": "Certificates regenerated",
+ "all_certificates_regenerated_message": "All certificates have been successfully regenerated.",
+ "type_server_name": "Type the server name '{server}' to confirm",
+ "regenerate": "Regenerate",
+ "server": "Server",
+ "ca": "CA"
},
"openvpn_tunnel": {
"title": "OpenVPN tunnel",
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 73d4d94f4..f7d138343 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -427,7 +427,9 @@
"duplicate_ip": "L'IP è già stato utilizzato",
"duplicate_port": "La porta è già stata utilizzata",
"cannot_restart_ipsec": "Impossibile riavviare il servizio IPsec",
- "cannot_regenerate_cert": "Impossibile rigenerare i certificati"
+ "cannot_regenerate_cert": "Impossibile rigenerare i certificati",
+ "cannot_renew_server_cert": "Impossibile rinnovare il certificato del server",
+ "cannot_regenerate_all_certs": "Impossibile rigenerare tutti i certificati"
},
"login": {
"sign_in": "Accedi",
@@ -1812,7 +1814,7 @@
"download_configuration": "Scarica configurazione",
"password_not_configured": "Password non configurata",
"reserved_ip": "IP riservato",
- "roadwarrior_accounts_description": "Gli utenti in questa lista posso collegarsi al server OpenVPN Road Warrior. Solamente gli utenti che hanno configurato una password possono utilizzare la modalità di autenticazione 'utente e password'. Se la modalità di autenticazione selezionata richiede un certificato x509, il certificato deve essere valido. Le modifiche agli utenti hanno effetto immediato.",
+ "roadwarrior_accounts_description": "Gli utenti in questa lista possono collegarsi al server OpenVPN Road Warrior. A seconda della modalità di autenticazione, è richiesta una password e/o un certificato x509 valido. Le modifiche hanno effetto immediato.",
"disable_account_message": "L'utente è attualmente collegato via VPN. Disabilitando questo utente, la sessione VPN corrente verrà terminata.",
"create_accounts_for_all_users": "Crea un account per ogni utente",
"no_openvpn_rw_server_found": "Nessun server OpenVPN Road Warrior trovato",
@@ -1863,7 +1865,23 @@
"road_warrior_server": "Server Road Warrior",
"connections_history": "Cronologia delle connessioni"
},
- "user_not_valid": "Utente non valido: l'utente ha una configurazione VPN ma non è presente all'interno del database utenti remoto."
+ "user_not_valid": "Utente non valido: l'utente ha una configurazione VPN ma non è presente all'interno del database utenti remoto.",
+ "certificates_expiration": "Scadenza certificati",
+ "certificate_expiration": "Scadenza certificato",
+ "certificate_expiring_tooltip": "Il certificato scadrà tra {days} giorni",
+ "certificate_expired_tooltip": "Il certificato è scaduto",
+ "renew_server_certificate": "Rinnova certificato server",
+ "regenerate_all_certificates": "Rigenera tutti i certificati",
+ "renew_server_certificate_message": "Questa azione rinnoverà il certificato del server utilizzando l'Autorità di Certificazione (CA) esistente. Le configurazioni e i certificati dei client rimarranno validi.",
+ "regenerate_all_certificates_message": "Questa azione rigenererà l'Autorità di Certificazione (CA) e tutti i certificati del server. Tutti i certificati esistenti diventeranno invalidi e i client dovranno scaricare e installare i nuovi file di configurazione.",
+ "server_certificate_renewed": "Certificato server rinnovato",
+ "server_certificate_renewed_message": "Il certificato del server è stato rinnovato con successo.",
+ "all_certificates_regenerated": "Certificati rigenerati",
+ "all_certificates_regenerated_message": "I certificati sono stati rigenerati con successo.",
+ "type_server_name": "Digita il nome del server '{server}' per confermare",
+ "regenerate": "Rigenera",
+ "server": "Server",
+ "ca": "CA"
},
"qos": {
"interface": "Interfaccia",
diff --git a/src/views/standalone/vpn/OpenvpnRoadWarriorView.vue b/src/views/standalone/vpn/OpenvpnRoadWarriorView.vue
index ca5619102..bde72beea 100644
--- a/src/views/standalone/vpn/OpenvpnRoadWarriorView.vue
+++ b/src/views/standalone/vpn/OpenvpnRoadWarriorView.vue
@@ -24,7 +24,11 @@ import { useUciPendingChangesStore } from '@/stores/standalone/uciPendingChanges
import { computed } from 'vue'
import DeleteRWServerModal from '@/components/standalone/openvpn_rw/DeleteRWServerModal.vue'
import CreateOrEditRWServerDrawer from '@/components/standalone/openvpn_rw/CreateOrEditRWServerDrawer.vue'
+import RenewRWServerCertificateModal from '@/components/standalone/openvpn_rw/RenewRWServerCertificateModal.vue'
+import RegenerateRWAllCertificatesModal from '@/components/standalone/openvpn_rw/RegenerateRWAllCertificatesModal.vue'
+
import ConnectionsHistory from '@/components/standalone/openvpn_rw/ConnectionsHistory.vue'
+import { useNotificationsStore } from '@/stores/notifications'
export type RWAuthenticationMode =
| 'username_password'
@@ -55,6 +59,10 @@ export type RWServer = {
compress?: string
ns_description: string
ifconfig_pool?: string[]
+ certificates: {
+ CA?: number
+ server?: number
+ }
}
export type RWAccount = {
@@ -88,6 +96,7 @@ const RELOAD_INTERVAL = 10000
const loading = ref(true)
const loadingUsers = ref(true)
const instanceName = ref('')
+const serverName = ref('')
const instanceData = ref()
const users = ref([])
const error = ref({
@@ -95,12 +104,15 @@ const error = ref({
notificationDescription: '',
notificationDetails: ''
})
+const notificationsStore = useNotificationsStore()
const isInitializingInstance = ref(false)
const showDeleteServerModal = ref(false)
const showCreateOrEditServerModal = ref(false)
const loadingError = ref(false)
const fetchServerIntervalId = ref(0)
+const showRenewServerCertificateModal = ref(false)
+const showRegenerateAllCertificatesModal = ref(false)
const connectedClients = computed(() => users.value.filter((x) => x.connected).length)
@@ -147,6 +159,7 @@ async function fetchServer(setLoading: boolean = true) {
await ubusCall('ns.ovpnrw', 'get-configuration', { instance: instanceName.value })
).data
if (instanceData.value?.ns_description) {
+ serverName.value = instanceData.value.ns_description
await fetchUsers(setLoading)
}
}
@@ -192,6 +205,19 @@ async function initAndConfigureServer() {
showCreateOrEditServerModal.value = true
}
+async function showNotificationAndReload(title: string, description: string) {
+ // show toast notification
+ setTimeout(() => {
+ notificationsStore.createNotification({
+ title: title,
+ description: description,
+ kind: 'success'
+ })
+ }, 500)
+ // reload server data
+ await reloadServer()
+}
+
/**
* When the ui pools the server loading, sometimes the load time is pretty big. The following
* variable is only used in the interval pool below, so that the issue is mitigated.
@@ -283,6 +309,8 @@ onUnmounted(() => {
:server="instanceData"
@delete-server="showDeleteServerModal = true"
@edit-server="showCreateOrEditServerModal = true"
+ @renew-server-certificate="showRenewServerCertificateModal = true"
+ @regenerate-all-certificates="showRegenerateAllCertificatesModal = true"
/>
{
@close="showCreateOrEditServerModal = false"
@add-edit-server="reloadServer"
/>
+
+