From 9976dff2f407c0a8621ba5b8ddb31a12adbec387 Mon Sep 17 00:00:00 2001 From: Simon Kobyda Date: Fri, 14 Jul 2023 13:45:12 +0200 Subject: [PATCH] Authorized SSH keys for VM's based on cloud image --- .../create-vm-dialog/createVmDialog.jsx | 100 ++++++++++++++++-- src/libvirtApi/domain.js | 4 +- src/scripts/install_machine.py | 4 + test/check-machines-create | 71 +++++++++++++ 4 files changed, 170 insertions(+), 9 deletions(-) diff --git a/src/components/create-vm-dialog/createVmDialog.jsx b/src/components/create-vm-dialog/createVmDialog.jsx index 1ddac2b7e..df6304952 100644 --- a/src/components/create-vm-dialog/createVmDialog.jsx +++ b/src/components/create-vm-dialog/createVmDialog.jsx @@ -24,6 +24,7 @@ import { Divider } from "@patternfly/react-core/dist/esm/components/Divider"; import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex"; import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form"; import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect"; +import { Grid, GridItem } from "@patternfly/react-core/dist/esm/layouts/Grid"; import { InputGroup } from "@patternfly/react-core/dist/esm/components/InputGroup"; import { Modal } from "@patternfly/react-core/dist/esm/components/Modal"; import { Select as PFSelect, SelectGroup, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select"; @@ -33,7 +34,7 @@ import { Button } from "@patternfly/react-core/dist/esm/components/Button"; import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip"; import { TextArea } from "@patternfly/react-core/dist/esm/components/TextArea"; import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner"; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; +import { ExternalLinkAltIcon, TrashIcon } from '@patternfly/react-icons'; import { DialogsContext } from 'dialogs.jsx'; import cockpit from 'cockpit'; @@ -84,6 +85,7 @@ import { domainCreate } from '../../libvirtApi/domain.js'; import { storagePoolRefresh } from '../../libvirtApi/storagePool.js'; import { getAccessToken } from '../../libvirtApi/rhel-images.js'; import { PasswordFormFields, password_quality } from 'cockpit-components-password.jsx'; +import { DynamicListForm } from 'DynamicListForm.jsx'; import './createVmDialog.scss'; @@ -248,6 +250,8 @@ function validateParams(vmParams) { } if (vmParams.userPassword && !vmParams.userLogin) { validationFailed.userLogin = _("User login must not be empty when user password is set"); + } else if (vmParams.sshKeys.length > 0 && !vmParams.userLogin) { + validationFailed.userLogin = _("User login must not be empty when SSH keys are set"); } return validationFailed; @@ -748,6 +752,76 @@ const UsersConfigurationRow = ({ ); }; +// This method needs to be outside of component as re-render would create a new instance of debounce +const parseKey = debounce(500, (key, setKeyObject) => { + if (isEmpty(key)) + return; + + // Validate correctness of the key + return cockpit.spawn(["ssh-keygen", "-l", "-f", "-"], { error: "message" }) + .input(key) + .then(() => { + const parts = key.split(" "); + if (parts.length > 2) { + setKeyObject({ + type: parts[0], + data: parts[1], + comment: parts[2], // comment is optional in SSH-format + }); + } + }) + .catch(() => { + setKeyObject(undefined); + console.warn("Could not validate the public key"); + }); +}); + +const SshKeysRow = ({ + id, item, onChange, idx, removeitem, +}) => { + const [keyObject, setKeyObject] = useState(); + + const onChangeHelper = (value) => { + // Some users might want to input multiple keys into the same field + // While handling that in the future might be a nice user experience, now we only parse one key out of input + value = value.split(/\r?\n/)[0]; + + onChange(idx, "value", value); + parseKey(value, setKeyObject); + }; + + return ( + + + {keyObject + ? + {keyObject.comment} + {keyObject.comment ? " - " + keyObject.type : keyObject.type} +
{keyObject.data}
+
+ : +