From 18a74570b89dc684f0dd8cd3753b7b8714381540 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 --- Makefile | 2 +- .../create-vm-dialog/createVmDialog.jsx | 104 ++++++++++++++++-- src/libvirtApi/domain.js | 4 +- src/scripts/install_machine.py | 4 + test/check-machines-create | 91 +++++++++++++++ 5 files changed, 195 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 964bf7ad4..59e908143 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ COCKPIT_REPO_FILES = \ $(NULL) COCKPIT_REPO_URL = https://github.com/cockpit-project/cockpit.git -COCKPIT_REPO_COMMIT = 9ee07a9856d6800f0c52ebe75b8b97e034199406 # 301 + PF5.1 +COCKPIT_REPO_COMMIT = fffd442c6335692c17be4572968320da86fa41f3 # 302 + 2 commits $(COCKPIT_REPO_FILES): $(COCKPIT_REPO_STAMP) COCKPIT_REPO_TREE = '$(strip $(COCKPIT_REPO_COMMIT))^{tree}' diff --git a/src/components/create-vm-dialog/createVmDialog.jsx b/src/components/create-vm-dialog/createVmDialog.jsx index 1ddac2b7e..34450df59 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,80 @@ 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, setKeyInvalid) => { + if (isEmpty(key)) + return; + + // Validate correctness of the key + return cockpit.spawn(["ssh-keygen", "-l", "-f", "-"], { error: "message" }) + .input(key) + .then(() => { + setKeyInvalid(false); + 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); + setKeyInvalid(true); + console.warn("Could not validate the public key"); + }); +}); + +const SshKeysRow = ({ + id, item, onChange, idx, removeitem, +}) => { + const [keyObject, setKeyObject] = useState(); + const [keyInvalid, setKeyInvalid] = useState(false); + + 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, setKeyInvalid); + }; + + return ( + + + {keyObject + ? + {keyObject.comment} + {keyObject.comment ? " - " + keyObject.type : keyObject.type} +
{keyObject.data}
+
+ : +