-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
515b6bc
commit 9714efd
Showing
6 changed files
with
354 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import React, { useContext, useEffect, useState } from 'react'; | ||
import { Name, NetworkModal, dialogSave } from "./dialogs-common"; | ||
import { FileUpload } from '@patternfly/react-core/dist/esm/components/FileUpload/index.js'; | ||
import { FormGroup } from '@patternfly/react-core/dist/esm/components/Form/index.js'; | ||
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput/index.js'; | ||
import cockpit from 'cockpit'; | ||
import { ModelContext } from './model-context'; | ||
import { useDialogs } from 'dialogs.jsx'; | ||
|
||
const _ = cockpit.gettext; | ||
|
||
// TODO: clean it | ||
async function ovpnToJSON(ovpn) { | ||
const configFile = "/tmp/config.ovpn"; // TODO: use a random name for temporary files | ||
await cockpit.file(configFile).replace(ovpn); | ||
await cockpit.script(`nmcli con import --temporary type openvpn file ${configFile}`, { superuser: 'try' }); | ||
await cockpit.file("/tmp/ini-to-json.py").replace( | ||
`import configparser | ||
import json | ||
config_object = configparser.ConfigParser() | ||
file = open("/run/NetworkManager/system-connections/config.nmconnection","r") | ||
config_object.read_file(file) | ||
output_dict=dict() | ||
sections=config_object.sections() | ||
for section in sections: | ||
items=config_object.items(section) | ||
output_dict[section]=dict(items) | ||
json_string=json.dumps(output_dict) | ||
print(json_string) | ||
file.close() | ||
`); | ||
const json = await cockpit.script("python /tmp/ini-to-json.py", { superuser: 'try' }); | ||
// clean up the temporary files | ||
await cockpit.script("rm /tmp/config.ovpn /tmp/ini-to-json.py", { superuser: 'try' }); | ||
await cockpit.script("nmcli con del config", { superuser: 'try' }); | ||
return json; | ||
} | ||
|
||
export function OpenVPNDialog({ settings, connection, dev }) { | ||
const Dialogs = useDialogs(); | ||
const idPrefix = "network-openvpn-settings"; | ||
const model = useContext(ModelContext); | ||
|
||
const [iface, setIface] = useState(settings.connection.interface_name); | ||
const [configName, setConfigName] = useState(""); | ||
const [configVal, setConfigVal] = useState(""); | ||
const [caCertName, setCaCertName] = useState(""); | ||
const [caCertVal, setCaCertVal] = useState(""); | ||
const [userCertName, setUserCertName] = useState(""); | ||
const [userCertVal, setUserCertVal] = useState(""); | ||
const [userKeyName, setUserKeyName] = useState(""); | ||
const [userPrivateKey, setUserPrivateKey] = useState(""); | ||
const [dialogError, setDialogError] = useState(""); | ||
const [vpnSetings, setVpnSettings] = useState({ | ||
remote: '', | ||
}); // TODO: eventually there should a list of proper defaults instead of an empty object | ||
|
||
useEffect(() => { | ||
if (!configVal) return; | ||
|
||
async function getConfigJSON() { | ||
try { | ||
const json = await ovpnToJSON(configVal); | ||
const vpnObj = JSON.parse(json).vpn; | ||
setVpnSettings(vpnObj); | ||
setCaCertName(vpnObj.ca.split("/").at(-1)); | ||
const ca = await cockpit.file(vpnObj.ca, { superuser: 'try' }).read(); | ||
setCaCertVal(ca); | ||
setUserCertName(vpnObj.cert.split("/").at(-1)); | ||
const cert = await cockpit.file(vpnObj.cert, { superuser: 'try' }).read(); | ||
setUserCertVal(cert); | ||
setUserKeyName(vpnObj.key.split("/").at(-1)); | ||
const key = await cockpit.file(vpnObj.key, { superuser: 'try' }).read(); | ||
setUserPrivateKey(key); | ||
} catch (e) { | ||
setDialogError(e.message); | ||
} | ||
} | ||
getConfigJSON(); | ||
}, [configVal]); | ||
|
||
async function onSubmit() { | ||
const user = await cockpit.user(); | ||
const caPath = `${user.home}/.cert/${caCertName}-ca.pem`; | ||
const userCertPath = `${user.home}/.cert/${userCertName}-cert.pem`; | ||
const userKeyPath = `${user.home}/.cert/${userKeyName}-key.pem`; | ||
|
||
try { | ||
// check if remote or certificates are empty | ||
if (!vpnSetings.remote.trim()) | ||
throw new Error(_("Remote cannot be empty.")); | ||
if (!caCertVal.trim()) | ||
throw new Error(_("CA certificate is empty.")); | ||
if (!userCertVal.trim()) | ||
throw new Error(_("User certificate is empty.")); | ||
if (!userPrivateKey.trim()) | ||
throw new Error(_("User private key is empty.")); | ||
|
||
await cockpit.script(`mkdir -p ${user.home}/.cert/nm-openvpn`); | ||
// await cockpit.script(`touch ${caPath} ${userCertPath} ${userKeyPath}`); | ||
await cockpit.file(caPath).replace(caCertVal); | ||
await cockpit.file(userCertPath).replace(userCertVal); | ||
await cockpit.file(userKeyPath).replace(userPrivateKey); | ||
} catch (e) { | ||
setDialogError(e.message); | ||
return; | ||
} | ||
|
||
function createSettingsObject() { | ||
return { | ||
...settings, | ||
connection: { | ||
...settings.connection, | ||
type: 'vpn', | ||
}, | ||
vpn: { | ||
data: { | ||
...vpnSetings, | ||
ca: caPath, | ||
cert: userCertPath, | ||
key: userKeyPath, | ||
'connection-type': 'tls', // this is not an openvpn option, rather specific to NM | ||
}, | ||
'service-type': 'org.freedesktop.NetworkManager.openvpn' | ||
} | ||
}; | ||
} | ||
|
||
dialogSave({ | ||
connection, | ||
dev, | ||
model, | ||
settings: createSettingsObject(), | ||
onClose: Dialogs.close, | ||
setDialogError, | ||
}); | ||
} | ||
|
||
return ( | ||
<NetworkModal | ||
title={!connection ? _("Add OpenVPN") : _("Edit OpenVPN settings")} | ||
isCreateDialog={!connection} | ||
onSubmit={onSubmit} | ||
dialogError={dialogError} | ||
idPrefix={idPrefix} | ||
> | ||
<Name idPrefix={idPrefix} iface={iface} setIface={setIface} /> | ||
<FormGroup label={_("OpenVPN config")} id={idPrefix + '-config-group'}> | ||
<FileUpload id={idPrefix + '-config'} filename={configName} onFileInputChange={(_, file) => setConfigName(file.name)} type='text' onDataChange={(_, val) => setConfigVal(val)} hideDefaultPreview onClearClick={() => { setConfigName(''); setConfigVal('') }} /> | ||
</FormGroup> | ||
<FormGroup label={_("Remote")}> | ||
<TextInput id={idPrefix + '-remote-input'} value={vpnSetings.remote} onChange={(_, val) => setVpnSettings(settings => ({ ...settings, remote: val }))} /> | ||
</FormGroup> | ||
<FormGroup label={_("CA certificate")} id={idPrefix + '-ca-group'}> | ||
<FileUpload id={idPrefix + '-ca'} filename={caCertName} onFileInputChange={(_, file) => setCaCertName(file.name)} type='text' onDataChange={(_, val) => setCaCertVal(val)} hideDefaultPreview onClearClick={() => { setCaCertName(''); setCaCertVal('') }} /> | ||
</FormGroup> | ||
<FormGroup label={_("User certificate")} id={idPrefix + '-user-cert-group'}> | ||
<FileUpload id={idPrefix + '-user-cert'} filename={userCertName} onFileInputChange={(_, file) => setUserCertName(file.name)} type='text' onDataChange={(_, val) => setUserCertVal(val)} hideDefaultPreview onClearClick={() => { setUserCertName(''); setUserCertVal('') }} /> | ||
</FormGroup> | ||
<FormGroup label={_("User private key")} id={idPrefix + '-private-key-group'}> | ||
<FileUpload id={idPrefix + '-user-key'} filename={userKeyName} onFileInputChange={(_, file) => setUserKeyName(file.name)} type='text' onDataChange={(_, val) => setUserPrivateKey(val)} hideDefaultPreview onClearClick={() => { setUserKeyName(''); setUserPrivateKey('') }} /> | ||
</FormGroup> | ||
</NetworkModal> | ||
); | ||
} | ||
|
||
export function getOpenVPNGhostSettings({ newIfaceName }) { | ||
return { | ||
connection: { | ||
id: `con-${newIfaceName}`, | ||
interface_name: newIfaceName, | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.