123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- /*
- * QEMU Guest Agent win32-specific command implementations for SSH keys.
- * The implementation is opinionated and expects the SSH implementation to
- * be OpenSSH.
- *
- * Copyright Schweitzer Engineering Laboratories. 2024
- *
- * Authors:
- * Aidan Leuck <aidan_leuck@selinc.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or later.
- * See the COPYING file in the top-level directory.
- */
- #include "qemu/osdep.h"
- #include <aclapi.h>
- #include <qga-qapi-types.h>
- #include "commands-common-ssh.h"
- #include "commands-windows-ssh.h"
- #include "guest-agent-core.h"
- #include "limits.h"
- #include "lmaccess.h"
- #include "lmapibuf.h"
- #include "lmerr.h"
- #include "qapi/error.h"
- #include "qga-qapi-commands.h"
- #include "sddl.h"
- #include "shlobj.h"
- #include "userenv.h"
- #define AUTHORIZED_KEY_FILE "authorized_keys"
- #define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys"
- #define LOCAL_SYSTEM_SID "S-1-5-18"
- #define ADMIN_SID "S-1-5-32-544"
- /*
- * Frees userInfo structure. This implements the g_auto cleanup
- * for the structure.
- */
- void free_userInfo(PWindowsUserInfo info)
- {
- g_free(info->sshDirectory);
- g_free(info->authorizedKeyFile);
- LocalFree(info->SSID);
- g_free(info->username);
- g_free(info);
- }
- /*
- * Gets the admin SSH folder for OpenSSH. OpenSSH does not store
- * the authorized_key file in the users home directory for security reasons and
- * instead stores it at %PROGRAMDATA%/ssh. This function returns the path to
- * that directory on the users machine
- *
- * parameters:
- * errp -> error structure to set when an error occurs
- * returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error
- * occurred.
- */
- static char *get_admin_ssh_folder(Error **errp)
- {
- /* Allocate memory for the program data path */
- g_autofree char *programDataPath = NULL;
- char *authkeys_path = NULL;
- PWSTR pgDataW = NULL;
- g_autoptr(GError) gerr = NULL;
- /* Get the KnownFolderPath on the machine. */
- HRESULT folderResult =
- SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW);
- if (folderResult != S_OK) {
- error_setg(errp, "Failed to retrieve ProgramData folder");
- return NULL;
- }
- /* Convert from a wide string back to a standard character string. */
- programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr);
- CoTaskMemFree(pgDataW);
- if (!programDataPath) {
- error_setg(errp,
- "Failed converting ProgramData folder path to UTF-16 %s",
- gerr->message);
- return NULL;
- }
- /* Build the path to the file. */
- authkeys_path = g_build_filename(programDataPath, "ssh", NULL);
- return authkeys_path;
- }
- /*
- * Gets the path to the SSH folder for the specified user. If the user is an
- * admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is
- * not an admin it returns %USERPROFILE%/.ssh
- *
- * parameters:
- * username -> Username to get the SSH folder for
- * isAdmin -> Whether the user is an admin or not
- * errp -> Error structure to set any errors that occur.
- * returns: path to the ssh folder as a string.
- */
- static char *get_ssh_folder(const char *username, const bool isAdmin,
- Error **errp)
- {
- DWORD maxSize = MAX_PATH;
- g_autofree char *profilesDir = g_new0(char, maxSize);
- if (isAdmin) {
- return get_admin_ssh_folder(errp);
- }
- /* If not an Admin the SSH key is in the user directory. */
- /* Get the user profile directory on the machine. */
- BOOL ret = GetProfilesDirectory(profilesDir, &maxSize);
- if (!ret) {
- error_setg_win32(errp, GetLastError(),
- "failed to retrieve profiles directory");
- return NULL;
- }
- /* Builds the filename */
- return g_build_filename(profilesDir, username, ".ssh", NULL);
- }
- /*
- * Creates an entry for the user so they can access the ssh folder in their
- * userprofile.
- *
- * parameters:
- * userInfo -> Information about the current user
- * pACL -> Pointer to an ACL structure
- * errp -> Error structure to set any errors that occur
- * returns -> 1 on success, 0 otherwise
- */
- static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
- {
- const int aclSize = 1;
- PACL newACL = NULL;
- EXPLICIT_ACCESS eAccess[1];
- PSID userPSID = NULL;
- /* Get a pointer to the internal SID object in Windows */
- bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
- if (!converted) {
- error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
- userInfo->username);
- goto error;
- }
- /* Set the permissions for the user. */
- eAccess[0].grfAccessPermissions = GENERIC_ALL;
- eAccess[0].grfAccessMode = SET_ACCESS;
- eAccess[0].grfInheritance = NO_INHERITANCE;
- eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
- eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
- eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID;
- /* Set the ACL entries */
- DWORD setResult;
- /*
- * If we are given a pointer that is already initialized, then we can merge
- * the existing entries instead of overwriting them.
- */
- if (*pACL) {
- setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL);
- } else {
- setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL);
- }
- if (setResult != ERROR_SUCCESS) {
- error_setg_win32(errp, GetLastError(),
- "failed to set ACL entries for user %s %lu",
- userInfo->username, setResult);
- goto error;
- }
- /* Free any old memory since we are going to overwrite the users pointer. */
- LocalFree(*pACL);
- *pACL = newACL;
- LocalFree(userPSID);
- return true;
- error:
- LocalFree(userPSID);
- return false;
- }
- /*
- * Creates a base ACL for both normal users and admins to share
- * pACL -> Pointer to an ACL structure
- * errp -> Error structure to set any errors that occur
- * returns: 1 on success, 0 otherwise
- */
- static bool create_acl_base(PACL *pACL, Error **errp)
- {
- PSID adminGroupPSID = NULL;
- PSID systemPSID = NULL;
- const int aclSize = 2;
- EXPLICIT_ACCESS eAccess[2];
- /* Create an entry for the system user. */
- const char *systemSID = LOCAL_SYSTEM_SID;
- bool converted = ConvertStringSidToSid(systemSID, &systemPSID);
- if (!converted) {
- error_setg_win32(errp, GetLastError(), "failed to retrieve system SID");
- goto error;
- }
- /* set permissions for system user */
- eAccess[0].grfAccessPermissions = GENERIC_ALL;
- eAccess[0].grfAccessMode = SET_ACCESS;
- eAccess[0].grfInheritance = NO_INHERITANCE;
- eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
- eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
- eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID;
- /* Create an entry for the admin user. */
- const char *adminSID = ADMIN_SID;
- converted = ConvertStringSidToSid(adminSID, &adminGroupPSID);
- if (!converted) {
- error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID");
- goto error;
- }
- /* Set permissions for admin group. */
- eAccess[1].grfAccessPermissions = GENERIC_ALL;
- eAccess[1].grfAccessMode = SET_ACCESS;
- eAccess[1].grfInheritance = NO_INHERITANCE;
- eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
- eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
- eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID;
- /* Put the entries in an ACL object. */
- PACL pNewACL = NULL;
- DWORD setResult;
- /*
- *If we are given a pointer that is already initialized, then we can merge
- *the existing entries instead of overwriting them.
- */
- if (*pACL) {
- setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL);
- } else {
- setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL);
- }
- if (setResult != ERROR_SUCCESS) {
- error_setg_win32(errp, GetLastError(),
- "failed to set base ACL entries for system user and "
- "admin group %lu",
- setResult);
- goto error;
- }
- LocalFree(adminGroupPSID);
- LocalFree(systemPSID);
- /* Free any old memory since we are going to overwrite the users pointer. */
- LocalFree(*pACL);
- *pACL = pNewACL;
- return true;
- error:
- LocalFree(adminGroupPSID);
- LocalFree(systemPSID);
- return false;
- }
- /*
- * Sets the access control on the authorized_keys file and any ssh folders that
- * need to be created. For administrators the required permissions on the
- * file/folders are that only administrators and the LocalSystem account can
- * access the folders. For normal user accounts only the specified user,
- * LocalSystem and Administrators can have access to the key.
- *
- * parameters:
- * userInfo -> pointer to structure that contains information about the user
- * PACL -> pointer to an access control structure that will be set upon
- * successful completion of the function.
- * errp -> error structure that will be set upon error.
- * returns: 1 upon success 0 upon failure.
- */
- static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
- {
- /*
- * Creates a base ACL that both admins and users will share
- * This adds the Administrators group and the SYSTEM group
- */
- if (!create_acl_base(pACL, errp)) {
- return false;
- }
- /*
- * If the user is not an admin give the user creating the key permission to
- * access the file.
- */
- if (!userInfo->isAdmin) {
- if (!create_acl_user(userInfo, pACL, errp)) {
- return false;
- }
- return true;
- }
- return true;
- }
- /*
- * Create the SSH directory for the user and d sets appropriate permissions.
- * In general the directory will be %PROGRAMDATA%/ssh if the user is an admin.
- * %USERPOFILE%/.ssh if not an admin
- *
- * parameters:
- * userInfo -> Contains information about the user
- * errp -> Structure that will contain errors if the function fails.
- * returns: zero upon failure, 1 upon success
- */
- static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp)
- {
- PACL pNewACL = NULL;
- g_autofree PSECURITY_DESCRIPTOR pSD = NULL;
- /* Gets the appropriate ACL for the user */
- if (!create_acl(userInfo, &pNewACL, errp)) {
- goto error;
- }
- /* Allocate memory for a security descriptor */
- pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
- if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) {
- error_setg_win32(errp, GetLastError(),
- "Failed to initialize security descriptor");
- goto error;
- }
- /* Associate the security descriptor with the ACL permissions. */
- if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) {
- error_setg_win32(errp, GetLastError(),
- "Failed to set security descriptor ACL");
- goto error;
- }
- /* Set the security attributes on the folder */
- SECURITY_ATTRIBUTES sAttr;
- sAttr.bInheritHandle = FALSE;
- sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
- sAttr.lpSecurityDescriptor = pSD;
- /* Create the directory with the created permissions */
- BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr);
- if (!created) {
- error_setg_win32(errp, GetLastError(), "failed to create directory %s",
- userInfo->sshDirectory);
- goto error;
- }
- /* Free memory */
- LocalFree(pNewACL);
- return true;
- error:
- LocalFree(pNewACL);
- return false;
- }
- /*
- * Sets permissions on the authorized_key_file that is created.
- *
- * parameters: userInfo -> Information about the user
- * errp -> error structure that will contain errors upon failure
- * returns: 1 upon success, zero upon failure.
- */
- static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp)
- {
- PACL pACL = NULL;
- PSID userPSID;
- /* Creates the access control structure */
- if (!create_acl(userInfo, &pACL, errp)) {
- goto error;
- }
- /* Get the PSID structure for the user based off the string SID. */
- bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
- if (!converted) {
- error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
- userInfo->username);
- goto error;
- }
- /* Prevents permissions from being inherited and use the DACL provided. */
- const SE_OBJECT_TYPE securityBitFlags =
- DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
- /* Set the ACL on the file. */
- if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT,
- securityBitFlags, userPSID, NULL, pACL,
- NULL) != ERROR_SUCCESS) {
- error_setg_win32(errp, GetLastError(),
- "failed to set file security for file %s",
- userInfo->authorizedKeyFile);
- goto error;
- }
- LocalFree(pACL);
- LocalFree(userPSID);
- return true;
- error:
- LocalFree(pACL);
- LocalFree(userPSID);
- return false;
- }
- /*
- * Writes the specified keys to the authenticated keys file.
- * parameters:
- * userInfo: Information about the user we are writing the authkeys file to.
- * authkeys: Array of keys to write to disk
- * errp: Error structure that will contain any errors if they occur.
- * returns: 1 if successful, 0 otherwise.
- */
- static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys,
- Error **errp)
- {
- g_autofree char *contents = NULL;
- g_autoptr(GError) err = NULL;
- contents = g_strjoinv("\n", authkeys);
- if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) {
- error_setg(errp, "failed to write to '%s': %s",
- userInfo->authorizedKeyFile, err->message);
- return false;
- }
- if (!set_file_permissions(userInfo, errp)) {
- return false;
- }
- return true;
- }
- /*
- * Retrieves information about a Windows user by their username
- *
- * parameters:
- * userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it
- * will be allocated with information about the user and need to be freed.
- * username -> Name of the user to lookup.
- * errp -> Contains any errors that occur.
- * returns: 1 upon success, 0 upon failure.
- */
- static bool get_user_info(PWindowsUserInfo *userInfo, const char *username,
- Error **errp)
- {
- DWORD infoLevel = 4;
- LPUSER_INFO_4 uBuf = NULL;
- g_autofree wchar_t *wideUserName = NULL;
- g_autoptr(GError) gerr = NULL;
- PSID psid = NULL;
- /*
- * Converts a string to a Windows wide string since the GetNetUserInfo
- * function requires it.
- */
- wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
- if (!wideUserName) {
- goto error;
- }
- /* allocate data */
- PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1);
- /* Set pointer so it can be cleaned up by the callee, even upon error. */
- *userInfo = uData;
- /* Find the information */
- NET_API_STATUS result =
- NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf);
- if (result != NERR_Success) {
- /* Give a friendlier error message if the user was not found. */
- if (result == NERR_UserNotFound) {
- error_setg(errp, "User %s was not found", username);
- goto error;
- }
- error_setg(errp,
- "Received unexpected error when asking for user info: Error "
- "Code %lu",
- result);
- goto error;
- }
- /* Get information from the buffer returned by NetUserGetInfo. */
- uData->username = g_strdup(username);
- uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN;
- psid = uBuf->usri4_user_sid;
- char *sidStr = NULL;
- /*
- * We store the string representation of the SID not SID structure in
- * memory. Callees wanting to use the SID structure should call
- * ConvertStringSidToSID.
- */
- if (!ConvertSidToStringSid(psid, &sidStr)) {
- error_setg_win32(errp, GetLastError(),
- "failed to get SID string for user %s", username);
- goto error;
- }
- /* Store the SSID */
- uData->SSID = sidStr;
- /* Get the SSH folder for the user. */
- char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp);
- if (sshFolder == NULL) {
- goto error;
- }
- /* Get the authorized key file path */
- const char *authorizedKeyFile =
- uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE;
- char *authorizedKeyPath =
- g_build_filename(sshFolder, authorizedKeyFile, NULL);
- uData->sshDirectory = sshFolder;
- uData->authorizedKeyFile = authorizedKeyPath;
- /* Free */
- NetApiBufferFree(uBuf);
- return true;
- error:
- if (uBuf) {
- NetApiBufferFree(uBuf);
- }
- return false;
- }
- /*
- * Gets the list of authorized keys for a user.
- *
- * parameters:
- * username -> Username to retrieve the keys for.
- * errp -> Error structure that will display any errors through QMP.
- * returns: List of keys associated with the user.
- */
- GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username,
- Error **errp)
- {
- GuestAuthorizedKeys *keys = NULL;
- g_auto(GStrv) authKeys = NULL;
- g_autoptr(GuestAuthorizedKeys) ret = NULL;
- g_auto(PWindowsUserInfo) userInfo = NULL;
- /* Gets user information */
- if (!get_user_info(&userInfo, username, errp)) {
- return NULL;
- }
- /* Reads authkeys for the user */
- authKeys = read_authkeys(userInfo->authorizedKeyFile, errp);
- if (authKeys == NULL) {
- return NULL;
- }
- /* Set the GuestAuthorizedKey struct with keys from the file */
- ret = g_new0(GuestAuthorizedKeys, 1);
- for (int i = 0; authKeys[i] != NULL; i++) {
- g_strstrip(authKeys[i]);
- if (!authKeys[i][0] || authKeys[i][0] == '#') {
- continue;
- }
- QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i]));
- }
- /*
- * Steal the pointer because it is up for the callee to deallocate the
- * memory.
- */
- keys = g_steal_pointer(&ret);
- return keys;
- }
- /*
- * Adds an ssh key for a user.
- *
- * parameters:
- * username -> User to add the SSH key to
- * strList -> Array of keys to add to the list
- * has_reset -> Whether the keys have been reset
- * reset -> Boolean to reset the keys (If this is set the existing list will be
- * cleared) and the other key reset. errp -> Pointer to an error structure that
- * will get returned over QMP if anything goes wrong.
- */
- void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
- bool has_reset, bool reset, Error **errp)
- {
- g_auto(PWindowsUserInfo) userInfo = NULL;
- g_auto(GStrv) authkeys = NULL;
- strList *k;
- size_t nkeys, nauthkeys;
- /* Make sure the keys given are valid */
- if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
- return;
- }
- /* Gets user information */
- if (!get_user_info(&userInfo, username, errp)) {
- return;
- }
- /* Determine whether we should reset the keys */
- reset = has_reset && reset;
- if (!reset) {
- /* Read existing keys into memory */
- authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL);
- }
- /* Check that the SSH key directory exists for the user. */
- if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) {
- BOOL success = create_ssh_directory(userInfo, errp);
- if (!success) {
- return;
- }
- }
- /* Reallocates the buffer to fit the new keys. */
- nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
- authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
- /* zero out the memory for the reallocated buffer */
- memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
- /* Adds the keys */
- for (k = keys; k != NULL; k = k->next) {
- /* Check that the key doesn't already exist */
- if (g_strv_contains((const gchar *const *)authkeys, k->value)) {
- continue;
- }
- authkeys[nauthkeys++] = g_strdup(k->value);
- }
- /* Write the authkeys to the file. */
- write_authkeys(userInfo, authkeys, errp);
- }
- /*
- * Removes an SSH key for a user
- *
- * parameters:
- * username -> Username to remove the key from
- * strList -> List of strings to remove
- * errp -> Contains any errors that occur.
- */
- void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
- Error **errp)
- {
- g_auto(PWindowsUserInfo) userInfo = NULL;
- g_autofree struct passwd *p = NULL;
- g_autofree GStrv new_keys = NULL; /* do not own the strings */
- g_auto(GStrv) authkeys = NULL;
- GStrv a;
- size_t nkeys = 0;
- /* Validates the keys passed in by the user */
- if (!check_openssh_pub_keys(keys, NULL, errp)) {
- return;
- }
- /* Gets user information */
- if (!get_user_info(&userInfo, username, errp)) {
- return;
- }
- /* Reads the authkeys for the user */
- authkeys = read_authkeys(userInfo->authorizedKeyFile, errp);
- if (authkeys == NULL) {
- return;
- }
- /* Create a new buffer to hold the keys */
- new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
- for (a = authkeys; *a != NULL; a++) {
- strList *k;
- /* Filters out keys that are equal to ones the user specified. */
- for (k = keys; k != NULL; k = k->next) {
- if (g_str_equal(k->value, *a)) {
- break;
- }
- }
- if (k != NULL) {
- continue;
- }
- new_keys[nkeys++] = *a;
- }
- /* Write the new authkeys to the file. */
- write_authkeys(userInfo, new_keys, errp);
- }
|