diff --git a/sdk/keyvault/assets.json b/sdk/keyvault/assets.json index 59fc49329..1fcadfa3e 100644 --- a/sdk/keyvault/assets.json +++ b/sdk/keyvault/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "cpp", "TagPrefix": "cpp/keyvault", - "Tag": "cpp/keyvault_b43656c9a5" + "Tag": "cpp/keyvault_e1582c490f" } diff --git a/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md index 1852a1d73..2889846fd 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features Added +- Add support for Backup/Restore operations for Key Vault HSM. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/keyvault/azure-security-keyvault-administration/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/CMakeLists.txt index 3eacddb46..5f70eb301 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-administration/CMakeLists.txt @@ -48,6 +48,8 @@ endif() set( AZURE_SECURITY_KEYVAULT_ADMINISTRATION_HEADER inc/azure/keyvault/administration.hpp + inc/azure/keyvault/administration/backup_client.hpp + inc/azure/keyvault/administration/backup_operation.hpp inc/azure/keyvault/administration/dll_import_export.hpp inc/azure/keyvault/administration/rest_client_models.hpp inc/azure/keyvault/administration/rtti.hpp @@ -57,6 +59,8 @@ set( set( AZURE_SECURITY_KEYVAULT_ADMINISTRATION_SOURCE + src/backup_client.cpp + src/backup_operation.cpp src/keyvault_settings_common_request.cpp src/private/administration_constants.hpp src/private/keyvault_settings_common_request.hpp @@ -106,8 +110,8 @@ if (BUILD_PERFORMANCE_TESTS) #add_subdirectory(test/perf) endif() -if(BUILD_SAMPLES_HSM) - add_subdirectory(test/samples) +if(BUILD_SAMPLES) + add_subdirectory(samples) endif() az_vcpkg_export( diff --git a/sdk/keyvault/azure-security-keyvault-administration/README.md b/sdk/keyvault/azure-security-keyvault-administration/README.md index 91557748e..403360922 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/README.md +++ b/sdk/keyvault/azure-security-keyvault-administration/README.md @@ -130,6 +130,115 @@ To update the value of any of the the available settings, we will call the Updat Setting updatedSetting = settingsClient.UpdateSetting(settingsList.Value[0].Name, options).Value; ``` +## Creating a BackupClient + +To create a new `BackupClient` to perform these operations, you need the endpoint to an Azure Key Vault HSM and credentials. + +Key Vault BackupClient client for C++ currently supports any `TokenCredential` for authenticating. + +```cpp + auto credential + = std::make_shared(); +``` + +Then, in the sample below, you can set `keyVaultUrl` based on an environment variable, configuration setting, or any way that works for your application. + +```cpp + // create client + BackupClient client(std::getenv("AZURE_KEYVAULT_HSM_URL"), credential); +``` +## Create the SasTokenParameter + +Since these operations require a blob storage for the backup/restore operations, a SAS token is required for the connection between the services(Key Vault and Storage). + +In this sample we rely on a couple of extra environment variables. + +```cpp + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token + = Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + Azure::Core::Url blobUrl + = Azure::Core::Url(Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_URL")); +``` + +## The Backup operation + +Since this is a long running operation the service provides endpoints to determine the status while the opperation is running. + +### Starting the backup operation + +```cpp +// Create a full backup using a user-provided SAS token to an Azure blob storage container. +auto backupResponse = client.FullBackup(blobUrl, sasTokenParameter).Value; + +std::cout << "Backup Job Id: " << backupResponse.Value().JobId << std::endl + << "Backup Status: " << backupResponse.Value().Status << std::endl; +``` + +### Backup operation waiting + +In order to wait for the operation to complete we will call the polling method. + +```cpp +// Wait for the operation to complete. +auto backupStatus = backupResponse.PollUntilDone(10s); + +std::cout << "Backup Job Id: " << backupStatus.Value.JobId << std::endl + << "Backup Status: " << backupStatus.Value.Status << std::endl; +``` + +## The FullRestore operation + +Similar to the backup operation after we initialize the operation we can check the status. + +### Starting the restore operation + +the restore operation requires a folder where a backup was previously performed along side the SAS token parameter. +```cpp +// Restore the full backup using a user-provided SAS token to an Azure blob storage container. +std::cout << "Folder to restore: " << folderToRestore << std::endl; +auto restoreResponse = client.FullRestore(blobUrl, folderToRestore, sasTokenParameter).Value; +std::cout << "Restore Job Id: " << restoreResponse.Value().JobId << std::endl + << "Restore Status: " << restoreResponse.Value().Status << std::endl; +``` + +### FullRestore operation waiting + +```cpp +// Wait for the operation to complete. +auto restoreStatus = restoreResponse.PollUntilDone(10s); +std::cout << "Restore Job Id: " << restoreStatus.Value.JobId << std::endl + << "Restore Status: " << restoreStatus.Value.Status << std::endl; +``` + +## The SelectiveRestore operation + +Similar to the backup operation after we initialize the operation we can check the status. + +### Starting the restore operation + +The selective restore operation requires a folder where a backup was previously performed along side the SAS token parameter. + +```cpp +// Restore the full backup using a user-provided SAS token to an Azure blob storage container. +std::string folderToRestore = ...; +std::cout << "Folder to restore: " << restoreBlobDetails.FolderToRestore << std::endl; +auto selectiveRestore = client.SelectiveKeyRestore("keyName", blobUrl, folderToRestore, sasTokenParameter); +std::cout << "Restore Job Id: " << restoreResponse.Value.JobId << std::endl + << "Restore Status: " << restoreResponse.Value.Status << std::endl; +``` + +### Selective restore operation completion + +```cpp +// Wait for the operation to complete. +auto selectiveStatus = selectiveRestore.PollUntilDone(10s); +std::cout << "Selective Restore Job Id: " << selectiveStatus.Value.JobId << std::endl + << "Selective Restore Status: " << selectiveStatus.Value.Status << std::endl; +``` + ## Contributing For details on contributing to this repository, see the [contributing guide][azure_sdk_for_cpp_contributing]. diff --git a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration.hpp b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration.hpp index 645cbeb55..2e1c66a35 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration.hpp +++ b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration.hpp @@ -8,6 +8,8 @@ #pragma once +#include "azure/keyvault/administration/backup_client.hpp" +#include "azure/keyvault/administration/backup_operation.hpp" #include "azure/keyvault/administration/dll_import_export.hpp" #include "azure/keyvault/administration/rest_client_models.hpp" #include "azure/keyvault/administration/rtti.hpp" diff --git a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_client.hpp b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_client.hpp new file mode 100644 index 000000000..768c3b07c --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_client.hpp @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma once +#include "azure/keyvault/administration/backup_operation.hpp" +#include "azure/keyvault/administration/rest_client_models.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Azure::Security::KeyVault::Administration::Models; + +namespace Azure { namespace Security { namespace KeyVault { namespace Administration { + /** + * @brief Backup restore client. + * + */ + class BackupClient final { + public: + /** + * @brief Destructor. + * + */ + virtual ~BackupClient() = default; + + /** + * @brief Construct a new BackupClient object + * + * @param vaultUrl The URL address where the client will send the requests to. + * @param credential The authentication method to use. + * @param options The options to customize the client behavior. + */ + explicit BackupClient( + std::string const& vaultUrl, + std::shared_ptr credential, + BackupClientOptions options = BackupClientOptions()); + + /** + * @brief Creates a full backup using a user-provided SAS token to an Azure blob storage + * container. + * + * @param blobContainerUrl The URL for the blob storage resource. + * @param sasToken Azure blob shared access signature token pointing to a + * valid Azure blob container where full backup needs to be + * stored. This token needs to be valid for at least next 24 + * hours from the time of making this call. + * @param context The context for the operation can be used for request cancellation. + * @return A backup restore operation. + */ + Response FullBackup( + Azure::Core::Url const& blobContainerUrl, + SasTokenParameter const& sasToken, + Core::Context const& context = {}); + + /** + * @brief Returns the status of full backup operation. + * + * @param jobId Identifier for the full backup operation. + * @param context The context for the operation can be used for request cancellation. + * @return Backup restore operation status. + */ + Response FullBackupStatus( + std::string const& jobId = "", + Core::Context const& context = {}); + + /** + * @brief Restores all key materials using the SAS token pointing to a previously stored Azure + * Blob storage backup folder. + * + * @param blobContainerUrl The URL for the blob storage resource, including the path to the blob + * @param folderToRestore The path to the blob container where the backup resides. + * @param sasToken Azure blob shared access signature token pointing to a valid Azure blob + * container where full backup needs to be stored. This token needs to be valid for at least + * next 24 hours from the time of making this call. + * @param context The context for the operation can be used for request cancellation. + * @return A backup restore operation. + */ + Response FullRestore( + Azure::Core::Url const& blobContainerUrl, + std::string folderToRestore, + SasTokenParameter const& sasToken, + Core::Context const& context = {}); + + /** + * @brief Returns the status of restore operation. + * + * @param jobId Identifier for the restore operation. + * @param context The context for the operation can be used for request cancellation. + * @return A backup restore operation status. + */ + Response RestoreStatus( + std::string const& jobId = "", + Core::Context const& context = {}); + + /** + * @brief Restores all key versions of a given key using user supplied SAS token pointing to a + * previously stored Azure Blob storage backup folder. + * + * @param keyName The name of the key to be restored from the user supplied backup. + * @param blobContainerUrl The URL for the blob storage resource, including the path to the blob + * @param folderToRestore The path to the blob container where the backup resides. + * @param sasToken Azure blob shared access signature token pointing to a valid Azure blob + * container where full backup needs to be stored. This token needs to be valid for at least + * next 24 hours from the time of making this call. + * @param context The context for the operation can be used for request cancellation. + * @return A backup restore operation. + */ + Response SelectiveKeyRestore( + std::string const& keyName, + Azure::Core::Url const& blobContainerUrl, + std::string folderToRestore, + SasTokenParameter const& sasToken, + Core::Context const& context = {}); + + private: + std::shared_ptr m_pipeline; + Azure::Core::Url m_vaultBaseUrl; + std::string m_apiVersion; + KeyVaultServiceError DeserializeKeyVaultServiceError( + Azure::Core::Json::_internal::json errorFragment); + BackupOperationStatus DeserializeBackupOperationStatus( + Azure::Core::Http::RawResponse const& rawResponse); + }; + +}}}} // namespace Azure::Security::KeyVault::Administration diff --git a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_operation.hpp b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_operation.hpp new file mode 100644 index 000000000..ab0450c48 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/backup_operation.hpp @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma once +#include "azure/keyvault/administration/rest_client_models.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Azure::Security::KeyVault::Administration::Models; + +namespace Azure { namespace Security { namespace KeyVault { namespace Administration { + class BackupClient; + + /** + * @brief BackupOperation : The backup / restore long running operation. + * @remark Used to handle both backup and restore operations due to the similarity in patterns + * and return values. + */ + class BackupOperation final : public Azure::Core::Operation { + private: + /* BackupOperation can be constructed only by friends classes (internal + * creation). The constructor is private and requires internal components.*/ + friend class Azure::Security::KeyVault::Administration::BackupClient; + + std::shared_ptr m_backupClient; + BackupOperationStatus m_value; + std::string m_continuationToken; + bool m_isBackupOperation = true; + + std::unique_ptr PollInternal( + Azure::Core::Context const& context) override; + + Azure::Response PollUntilDoneInternal( + std::chrono::milliseconds period, + Azure::Core::Context& context) override; + + /** + * @brief Only friend classes are permitted to construct a RecoverDeletedKeyOperation. This is + * because a KeyVaultPipelne is required and it is not exposed to customers. + * + * @param backupClient A #BackupClient that is used for getting status updates. + * @param status A BackupOperationStatus object. + * @param isBackupOperation A boolean indicating if the operation is a backup operation or a + * restore. + */ + BackupOperation( + std::shared_ptr const& backupClient, + BackupOperationStatus const& status, + bool isBackupOperation) + : m_backupClient{backupClient}, m_value{status}, m_continuationToken{status.JobId}, + m_isBackupOperation{isBackupOperation} {}; + /** + * @brief Only friend classes are permitted to construct a RecoverDeletedKeyOperation. This is + * because a KeyVaultPipelne is required and it is not exposed to customers. + * @param backupClient A BackupClient that is used for getting status updates. + * @param continuationToken A string that is used to resume the operation. + * @param isBackupOperation A boolean indicating if the operation is a backup operation or a + * restore. + */ + BackupOperation( + std::shared_ptr const& backupClient, + std::string const& continuationToken, + bool isBackupOperation) + : m_backupClient{backupClient}, m_continuationToken{continuationToken}, + m_isBackupOperation{isBackupOperation} {}; + + public: + /** + * @brief Get the BackupOperationStatus object. + * + * @remark The status contains the current progress result at the time of the call. + * + * @return A BackupOperationStatus object. + */ + BackupOperationStatus Value() const override { return m_value; } + + /** + * @brief Get the continuation token used for further status inquiries + * + * @return std::string + */ + std::string GetResumeToken() const override { return m_continuationToken; } + + /** + * @brief Create a BackupOperation from the \p resumeToken fetched from + * another `Operation`, updated to the the latest operation status. + * + * @remark After the operation is initialized, it is used to poll the last update from the + * server using the \p context. + * + * @param resumeToken A previously generated token used to resume the polling of the + * operation. + * @param client A BackupClient that is used for getting status updates. + * @param isBackupOperation A boolean indicating if the operation is a backup operation if + * false it is considered a restore operation. + * @param context A Azure::Core::Context controlling the request lifetime. + * @return BackupOperation + */ + static BackupOperation CreateFromResumeToken( + std::string const& resumeToken, + BackupClient const& client, + bool isBackupOperation, + Azure::Core::Context const& context = Azure::Core::Context()) + { + BackupOperation operation( + std::make_shared(client), resumeToken, isBackupOperation); + operation.Poll(context); + return operation; + } + }; +}}}} // namespace Azure::Security::KeyVault::Administration diff --git a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/rest_client_models.hpp b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/rest_client_models.hpp index 52ddccbcb..2eadeebf9 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/rest_client_models.hpp +++ b/sdk/keyvault/azure-security-keyvault-administration/inc/azure/keyvault/administration/rest_client_models.hpp @@ -74,4 +74,111 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Administra std::vector Value; }; + /** + * @brief Define the options to create an SDK Keys client. + * + */ + struct BackupClientOptions final : public Azure::Core::_internal::ClientOptions + { + /** + * @brief Service Version used. + * + */ + const std::string ApiVersion{"7.5"}; + }; + + /** + * @brief KeyVault Service Error model. + * + */ + struct KeyVaultServiceError final + { + /** + * @brief The error code. + * + */ + std::string Code; + /** + * @brief The error message. + * + */ + std::string Message; + }; + + /** + * @brief The full backup operation. + * + */ + struct BackupOperationStatus final + { + /** + * @brief Status of the backup operation. + * + */ + std::string Status; + /** + * @brief The status details of backup operation. + * + */ + Azure::Nullable StatusDetails; + /** + * @brief Error encountered, if any, during the full backup operation. + * + */ + Azure::Nullable Error; + /** + * @brief The start time of the backup operation in UTC. + * + */ + DateTime StartTime; + /** + * @brief The end time of the backup operation in UTC. + * + */ + Nullable EndTime; + /** + * @brief Identifier for the full backup operation. + * + */ + std::string JobId; + /** + * @brief The Azure blob storage container Uri which contains the full backup. + * + */ + std::string AzureStorageBlobContainerUri; + }; + + /** + * @brief Sas token parameter for backup and restore operations. + * + */ + struct SasTokenParameter final + { + /** + * @brief The SAS token pointing to an Azure Blob storage container. + * + */ + Nullable Token; + /** + * @brief Indicates which authentication method should be used. If set to true, Managed HSM + * will use the configured user-assigned managed identity to authenticate with Azure Storage. + * Otherwise, a SAS token has to be specified. + * + */ + Nullable UseManagedIdentity; + }; + + /** + * @brief Full backup status options. + * + */ + struct FullBackupStatusOptions final + { + /** + * @brief Identifier for the full backup operation. + * + */ + std::string JobId; + }; + }}}}} // namespace Azure::Security::KeyVault::Administration::Models diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/samples/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/samples/CMakeLists.txt similarity index 61% rename from sdk/keyvault/azure-security-keyvault-administration/test/samples/CMakeLists.txt rename to sdk/keyvault/azure-security-keyvault-administration/samples/CMakeLists.txt index ce2d41316..e8d79387c 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/test/samples/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/CMakeLists.txt @@ -4,4 +4,6 @@ cmake_minimum_required (VERSION 3.13) add_subdirectory(sample1-basic-operations) +add_subdirectory(sample2-full-backup-restore) +add_subdirectory(sample3-backup-selective-restore) diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations/CMakeLists.txt similarity index 100% rename from sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations/CMakeLists.txt rename to sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations/CMakeLists.txt diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations/sample1_administration.cpp b/sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations/sample1_administration.cpp similarity index 93% rename from sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations/sample1_administration.cpp rename to sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations/sample1_administration.cpp index 1ea93194a..f90757c8c 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations/sample1_administration.cpp +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations/sample1_administration.cpp @@ -24,7 +24,8 @@ int main() auto credential = std::make_shared(); // create client - SettingsClient settingsClient(std::getenv("AZURE_KEYVAULT_HSM_URL"), credential); + SettingsClient settingsClient( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_HSM_URL"), credential); try { diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample1_administration.md b/sdk/keyvault/azure-security-keyvault-administration/samples/sample1_administration.md index 2fded41b4..4131f99ea 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/samples/sample1_administration.md +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample1_administration.md @@ -55,4 +55,4 @@ Call UpdateSetting to modify an existing setting. Create an options object and i ## Source To see the full example source, see: -[Source Code](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/keyvault/azure-security-keyvault-administration/test/samples/sample1-basic-operations) +[Source Code](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/keyvault/azure-security-keyvault-administration/samples/sample1-basic-operations) diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/CMakeLists.txt new file mode 100644 index 000000000..17cbaf7d7 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +cmake_minimum_required (VERSION 3.13) + +project (sample2-full-backup-restore LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_executable ( + sample2-full-backup-restore + sample2_full_backup_restore.cpp +) +create_per_service_target_build_for_sample(keyvault sample2-full-backup-restore) + +target_link_libraries(sample2-full-backup-restore PRIVATE azure-security-keyvault-administration azure-identity get-env-helper) diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/sample2_full_backup_restore.cpp b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/sample2_full_backup_restore.cpp new file mode 100644 index 000000000..ecaee848e --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore/sample2_full_backup_restore.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @brief This sample provides the code implementation to use the Key Vault Settings SDK client for + * C++ to get one or more settings, and update a setting value. + * + * @remark The following environment variables must be set before running the sample. + * - AZURE_KEYVAULT_HSM_URL: To the Key Vault HSM URL. + * - AZURE_KEYVAULT_BACKUP_TOKEN : The SAS token to access the blob storage account for + * backup/restore + * - AZURE_KEYVAULT_BACKUP_URL : The URL to the blob storage account + * + */ + +#include +#include + +#include +#include +#include + +using namespace Azure::Security::KeyVault::Administration; +using namespace std::chrono_literals; + +int main() +{ + auto credential = std::make_shared(); + + // create client + BackupClient client( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_HSM_URL"), credential); + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token + = Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + Azure::Core::Url blobUrl = Azure::Core::Url( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_URL")); + + try + { + // Create a full backup using a user-provided SAS token to an Azure blob storage container. + auto backupResponse = client.FullBackup(blobUrl, sasTokenParameter).Value; + + std::cout << "Backup Job Id: " << backupResponse.Value().JobId << std::endl + << "Backup Status: " << backupResponse.Value().Status << std::endl; + // Wait for the operation to complete. + auto backupStatus = backupResponse.PollUntilDone(10s); + + std::cout << "Backup Job Id: " << backupStatus.Value.JobId << std::endl + << "Backup Status: " << backupStatus.Value.Status << std::endl; + + // Restore the full backup using a user-provided SAS token to an Azure blob storage container. + Azure::Core::Url url(backupStatus.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + std::cout << "Folder to restore: " << folderToRestore << std::endl; + auto restoreResponse = client.FullRestore(blobUrl, folderToRestore, sasTokenParameter).Value; + std::cout << "Restore Job Id: " << restoreResponse.Value().JobId << std::endl + << "Restore Status: " << restoreResponse.Value().Status << std::endl; + + // Wait for the operation to complete. + auto restoreStatus = restoreResponse.PollUntilDone(10s); + std::cout << "Restore Job Id: " << restoreStatus.Value.JobId << std::endl + << "Restore Status: " << restoreStatus.Value.Status << std::endl; + } + catch (Azure::Core::Credentials::AuthenticationException const& e) + { + std::cout << "Authentication Exception happened:" << std::endl << e.what() << std::endl; + return 1; + } + catch (Azure::Core::RequestFailedException const& e) + { + std::cout << "Key Vault Settings Client Exception happened:" << std::endl + << e.Message << std::endl; + return 1; + } + + return 0; +} diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample2_full_backup_restore.md b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2_full_backup_restore.md new file mode 100644 index 000000000..6d64f4241 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample2_full_backup_restore.md @@ -0,0 +1,91 @@ +# Getting, updating, settings + +This sample demonstrates how to perform full backup and full restore for an Azure Key Vault HSM. +To get started, you'll need a URI to an Azure Key Vault HSM. + +## Creating a BackupClient + +To create a new `BackupClient` to perform these operations, you need the endpoint to an Azure Key Vault HSM and credentials. + +Key Vault BackupClient client for C++ currently supports any `TokenCredential` for authenticating. + +```cpp + auto credential + = std::make_shared(); +``` + +Then, in the sample below, you can set `keyVaultUrl` based on an environment variable, configuration setting, or any way that works for your application. + +```cpp + // create client + BackupClient client(std::getenv("AZURE_KEYVAULT_HSM_URL"), credential); +``` +## Create the SasTokenParameter + +Since these operations require a blob storage for the backup/restore operations, a SAS token is required for the connection between the services(Key Vault and Storage). + +In this sample we rely on a couple of extra environment variables. + +```cpp + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token + = Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + Azure::Core::Url blobUrl = Azure::Core::Url( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_URL")); +``` + +## The Backup operation + +Since this is a long running operation the service provides endpoints to determine the status while the opperation is running. + +### Starting the backup operation + +```cpp +// Create a full backup using a user-provided SAS token to an Azure blob storage container. +auto backupResponse = client.FullBackup(blobUrl, sasTokenParameter).Value; + +std::cout << "Backup Job Id: " << backupResponse.Value().JobId << std::endl + << "Backup Status: " << backupResponse.Value().Status << std::endl; +``` + +### Backup operation waiting + +In order to wait for the operation to complete we will call the polling method. + +```cpp +// Wait for the operation to complete. +auto backupStatus = backupResponse.PollUntilDone(10s); + +std::cout << "Backup Job Id: " << backupStatus.Value.JobId << std::endl + << "Backup Status: " << backupStatus.Value.Status << std::endl; +``` + +## The FullRestore operation + +Similar to the backup operation after we initialize the operation we can check the status. + +### Starting the restore operation + +the restore operation requires a folder where a backup was previously performed along side the SAS token parameter. +```cpp +// Restore the full backup using a user-provided SAS token to an Azure blob storage container. +std::cout << "Folder to restore: " << folderToRestore << std::endl; +auto restoreResponse = client.FullRestore(blobUrl, folderToRestore, sasTokenParameter).Value; +std::cout << "Restore Job Id: " << restoreResponse.Value().JobId << std::endl + << "Restore Status: " << restoreResponse.Value().Status << std::endl; +``` + +### FullRestore operation waiting + +```cpp +// Wait for the operation to complete. +auto restoreStatus = restoreResponse.PollUntilDone(10s); +std::cout << "Restore Job Id: " << restoreStatus.Value.JobId << std::endl + << "Restore Status: " << restoreStatus.Value.Status << std::endl; +``` +## Source + +To see the full example source, see: +[Source Code](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/keyvault/azure-security-keyvault-administration/samples/sample2-full-backup-restore) diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/CMakeLists.txt new file mode 100644 index 000000000..d14eeaddf --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +cmake_minimum_required (VERSION 3.13) + +project (sample3-backup-selective-restore LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_executable ( + sample3-backup-selective-restore + sample3_backup_selective_restore.cpp +) +create_per_service_target_build_for_sample(keyvault sample3-backup-selective-restore) + +target_link_libraries(sample3-backup-selective-restore PRIVATE azure-security-keyvault-administration azure-identity get-env-helper) diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/sample3_backup_selective_restore.cpp b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/sample3_backup_selective_restore.cpp new file mode 100644 index 000000000..d14c5d96b --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore/sample3_backup_selective_restore.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @brief This sample provides the code implementation to use the Key Vault Settings SDK client for + * C++ to get one or more settings, and update a setting value. + * + * @remark The following environment variables must be set before running the sample. + * - AZURE_KEYVAULT_HSM_URL: To the Key Vault HSM URL. + * - AZURE_KEYVAULT_BACKUP_TOKEN : The SAS token to access the blob storage account for + * backup/restore + * - AZURE_KEYVAULT_BACKUP_URL : The URL to the blob storage account + * + */ + +#include +#include + +#include +#include +#include + +using namespace Azure::Security::KeyVault::Administration; +using namespace std::chrono_literals; + +int main() +{ + auto credential = std::make_shared(); + + // create client + BackupClient client( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_HSM_URL"), credential); + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token + = Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + Azure::Core::Url blobUrl = Azure::Core::Url( + Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_URL")); + // the key name to restore from backup + const std::string keyName = "trytry"; + try + { + // Create a full backup using a user-provided SAS token to an Azure blob storage container. + auto backupResponse = client.FullBackup(blobUrl, sasTokenParameter).Value; + + std::cout << "Backup Job Id: " << backupResponse.Value().JobId << std::endl + << "Backup Status: " << backupResponse.Value().Status << std::endl; + // Wait for the operation to complete. + auto backupStatus = backupResponse.PollUntilDone(10s); + + std::cout << "Backup Job Id: " << backupStatus.Value.JobId << std::endl + << "Backup Status: " << backupStatus.Value.Status << std::endl; + // Restore a selected key from the backup using a user-provided SAS token to an Azure blob + // storage container. + Azure::Core::Url url(backupStatus.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + std::cout << "Folder to restore: " << folderToRestore << std::endl; + auto selectiveRestore + = client.SelectiveKeyRestore("trytry", blobUrl, folderToRestore, sasTokenParameter).Value; + std::cout << "Selective Restore Job Id: " << selectiveRestore.Value().JobId << std::endl + << "Selective Restore Status: " << selectiveRestore.Value().Status << std::endl; + + // Wait for the operation to complete. + auto selectiveStatus = selectiveRestore.PollUntilDone(10s); + std::cout << "Selective Restore Job Id: " << selectiveStatus.Value.JobId << std::endl + << "Selective Restore Status: " << selectiveStatus.Value.Status << std::endl; + } + catch (Azure::Core::Credentials::AuthenticationException const& e) + { + std::cout << "Authentication Exception happened:" << std::endl << e.what() << std::endl; + return 1; + } + catch (Azure::Core::RequestFailedException const& e) + { + std::cout << "Key Vault Settings Client Exception happened:" << std::endl + << e.Message << std::endl; + return 1; + } + + return 0; +} diff --git a/sdk/keyvault/azure-security-keyvault-administration/samples/sample3_backup_selective_restore.md b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3_backup_selective_restore.md new file mode 100644 index 000000000..f6eea22c9 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/samples/sample3_backup_selective_restore.md @@ -0,0 +1,93 @@ +# Getting, updating, settings + +This sample demonstrates how to perform full backup and full restore for an Azure Key Vault HSM. +To get started, you'll need a URI to an Azure Key Vault HSM. + +## Creating a BackupClient + +To create a new `BackupClient` to perform these operations, you need the endpoint to an Azure Key Vault HSM and credentials. + +Key Vault BackupClient client for C++ currently supports any `TokenCredential` for authenticating. + +```cpp + auto credential + = std::make_shared(); +``` + +Then, in the sample below, you can set `keyVaultUrl` based on an environment variable, configuration setting, or any way that works for your application. + +```cpp + // create client + BackupClient client(std::getenv("AZURE_KEYVAULT_HSM_URL"), credential); +``` +## Create the SasTokenParameter + +Since these operations require a blob storage for the backup/restore operations, a SAS token is required for the connection between the services(Key Vault and Storage). + +In this sample we rely on a couple of extra environment variables. + +```cpp + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token + = Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + Azure::Core::Url blobUrl + = Azure::Core::Url(Azure::Core::_internal::Environment::GetVariable("AZURE_KEYVAULT_BACKUP_URL")); +``` + +## The Backup operation + +Since this is a long running operation the service provides endpoints to determine the status while the opperation is running. + +### Starting the backup operation + +```cpp +// Create a full backup using a user-provided SAS token to an Azure blob storage container. +auto backupResponse = client.FullBackup(blobUrl, sasTokenParameter).Value; + +std::cout << "Backup Job Id: " << backupResponse.Value().JobId << std::endl + << "Backup Status: " << backupResponse.Value().Status << std::endl; +``` + +### Backup operation waiting + +In order to wait for the operation to complete we will call the polling method. + +```cpp +// Wait for the operation to complete. +auto backupStatus = backupResponse.PollUntilDone(10s); + +std::cout << "Backup Job Id: " << backupStatus.Value.JobId << std::endl + << "Backup Status: " << backupStatus.Value.Status << std::endl; +``` + +## The SelectiveRestore operation + +Similar to the backup operation after we initialize the operation we can check the status. + +### Starting the restore operation + +The selective restore operation requires a folder where a backup was previously performed along side the SAS token parameter. + +```cpp +// Restore the full backup using a user-provided SAS token to an Azure blob storage container. +std::string folderToRestore = ...; +std::cout << "Folder to restore: " << restoreBlobDetails.FolderToRestore << std::endl; +auto selectiveRestore = client.SelectiveKeyRestore("keyName", blobUrl, folderToRestore, sasTokenParameter); +std::cout << "Restore Job Id: " << restoreResponse.Value.JobId << std::endl + << "Restore Status: " << restoreResponse.Value.Status << std::endl; +``` + +### Selective restore operation completion + +```cpp +// Wait for the operation to complete. +auto selectiveStatus = selectiveRestore.PollUntilDone(10s); +std::cout << "Selective Restore Job Id: " << selectiveStatus.Value.JobId << std::endl + << "Selective Restore Status: " << selectiveStatus.Value.Status << std::endl; +``` +## Source + +To see the full example source, see: +[Source Code](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/keyvault/azure-security-keyvault-administration/samples/sample3-backup-selective-restore) diff --git a/sdk/keyvault/azure-security-keyvault-administration/src/backup_client.cpp b/sdk/keyvault/azure-security-keyvault-administration/src/backup_client.cpp new file mode 100644 index 000000000..37f1dd1e9 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/src/backup_client.cpp @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +#include "azure/keyvault/administration/backup_client.hpp" + +#include "private/administration_constants.hpp" +#include "private/package_version.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Azure::Security::KeyVault::Administration; +using namespace Azure::Core::Http; +using namespace Azure::Core::Http::Policies; +using namespace Azure::Core::Http::Policies::_internal; +using namespace Azure::Core::Http::_internal; + +BackupClient::BackupClient( + std::string const& vaultUrl, + std::shared_ptr credential, + BackupClientOptions options) + : m_vaultBaseUrl(vaultUrl), m_apiVersion(options.ApiVersion) +{ + std::vector> perRetrypolicies; + { + Azure::Core::Credentials::TokenRequestContext tokenContext; + tokenContext.Scopes = {_internal::UrlScope::GetScopeFromUrl(m_vaultBaseUrl)}; + + perRetrypolicies.emplace_back( + std::make_unique<_internal::KeyVaultChallengeBasedAuthenticationPolicy>( + credential, std::move(tokenContext))); + } + std::vector> perCallpolicies; + + m_pipeline = std::make_shared( + options, + _detail::KeyVaultServicePackageName, + _detail::PackageVersion::ToString(), + std::move(perRetrypolicies), + std::move(perCallpolicies)); +} + +Azure::Response BackupClient::FullBackup( + Azure::Core::Url const& blobContainerUrl, + SasTokenParameter const& sasToken, + Core::Context const& context) +{ + auto url = m_vaultBaseUrl; + url.AppendPath("backup"); + + url.SetQueryParameters({{"api-version", m_apiVersion}}); + + std::string jsonBody; + { + auto jsonRoot = Core::Json::_internal::json::object(); + + jsonRoot["storageResourceUri"] = blobContainerUrl.GetAbsoluteUrl(); + + if (sasToken.Token.HasValue()) + { + jsonRoot["token"] = sasToken.Token.Value(); + } + + if (sasToken.UseManagedIdentity.HasValue()) + { + jsonRoot["useManagedIdentity"] = sasToken.UseManagedIdentity.Value(); + } + + jsonBody = jsonRoot.dump(); + } + + Core::IO::MemoryBodyStream requestBody( + reinterpret_cast(jsonBody.data()), jsonBody.length()); + + Core::Http::Request request(Core::Http::HttpMethod::Post, url, &requestBody); + + request.SetHeader(HttpShared::ContentType, HttpShared::ApplicationJson); + request.SetHeader(HttpShared::Accept, HttpShared::ApplicationJson); + request.SetHeader("Content-Length", std::to_string(requestBody.Length())); + + auto rawResponse = m_pipeline->Send(request, context); + auto const httpStatusCode = rawResponse->GetStatusCode(); + + if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) + { + throw Core::RequestFailedException(rawResponse); + } + + BackupOperationStatus response = DeserializeBackupOperationStatus(*rawResponse); + BackupOperation operation(std::make_shared(*this), std::move(response), true); + return Response(std::move(operation), std::move(rawResponse)); +} + +Azure::Response BackupClient::FullBackupStatus( + std::string const& jobId, + Core::Context const& context) +{ + auto url = m_vaultBaseUrl; + url.AppendPath("backup"); + url.AppendPath(Core::Url::Encode(jobId)); + url.AppendPath("pending"); + + url.SetQueryParameters({{"api-version", m_apiVersion}}); + + Core::Http::Request request(Core::Http::HttpMethod::Get, url); + request.SetHeader(HttpShared::ContentType, HttpShared::ApplicationJson); + request.SetHeader(HttpShared::Accept, HttpShared::ApplicationJson); + + auto rawResponse = m_pipeline->Send(request, context); + auto const httpStatusCode = rawResponse->GetStatusCode(); + + if (httpStatusCode != Core::Http::HttpStatusCode::Ok) + { + throw Core::RequestFailedException(rawResponse); + } + + BackupOperationStatus response + + = DeserializeBackupOperationStatus(*rawResponse); + + return Response(std::move(response), std::move(rawResponse)); +} + +Azure::Response BackupClient::FullRestore( + Azure::Core::Url const& blobContainerUrl, + std::string folderToRestore, + SasTokenParameter const& sasToken, + Core::Context const& context) +{ + auto url = m_vaultBaseUrl; + url.AppendPath("restore"); + + url.SetQueryParameters({{"api-version", m_apiVersion}}); + + std::string jsonBody; + { + auto jsonRoot = Core::Json::_internal::json::object(); + + jsonRoot["sasTokenParameters"]["storageResourceUri"] = blobContainerUrl.GetAbsoluteUrl(); + + if (sasToken.Token.HasValue()) + { + jsonRoot["sasTokenParameters"]["token"] = sasToken.Token.Value(); + } + + if (sasToken.UseManagedIdentity.HasValue()) + { + jsonRoot["sasTokenParameters"]["useManagedIdentity"] = sasToken.UseManagedIdentity.Value(); + } + + jsonRoot["folderToRestore"] = folderToRestore; + + jsonBody = jsonRoot.dump(); + } + + Core::IO::MemoryBodyStream requestBody( + reinterpret_cast(jsonBody.data()), jsonBody.length()); + + Core::Http::Request request(Core::Http::HttpMethod::Put, url, &requestBody); + + request.SetHeader("Content-Type", "application/json"); + request.SetHeader("Content-Length", std::to_string(requestBody.Length())); + + auto rawResponse = m_pipeline->Send(request, context); + auto const httpStatusCode = rawResponse->GetStatusCode(); + + if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) + { + throw Core::RequestFailedException(rawResponse); + } + + BackupOperationStatus response = DeserializeBackupOperationStatus(*rawResponse); + BackupOperation operation(std::make_shared(*this), std::move(response), false); + return Response(std::move(operation), std::move(rawResponse)); +} + +Azure::Response BackupClient::RestoreStatus( + std::string const& jobId, + Core::Context const& context) +{ + auto url = m_vaultBaseUrl; + url.AppendPath("restore"); + url.AppendPath(Core::Url::Encode(jobId)); + url.AppendPath("pending"); + + url.SetQueryParameters({{"api-version", "7.5"}}); + + Core::Http::Request request(Core::Http::HttpMethod::Get, url); + + auto rawResponse = m_pipeline->Send(request, context); + auto const httpStatusCode = rawResponse->GetStatusCode(); + + if (httpStatusCode != Core::Http::HttpStatusCode::Ok) + { + throw Core::RequestFailedException(rawResponse); + } + + BackupOperationStatus response = DeserializeBackupOperationStatus(*rawResponse); + + return Response(std::move(response), std::move(rawResponse)); +} + +Azure::Response BackupClient::SelectiveKeyRestore( + std::string const& keyName, + Azure::Core::Url const& blobContainerUrl, + std::string folderToRestore, + SasTokenParameter const& sasToken, + Core::Context const& context) +{ + auto url = m_vaultBaseUrl; + url.AppendPath("keys"); + url.AppendPath(Core::Url::Encode(keyName)); + url.AppendPath("restore"); + + url.SetQueryParameters({{"api-version", "7.5"}}); + + std::string jsonBody; + { + auto jsonRoot = Core::Json::_internal::json::object(); + + jsonRoot["sasTokenParameters"]["storageResourceUri"] = blobContainerUrl.GetAbsoluteUrl(); + + if (sasToken.Token.HasValue()) + { + jsonRoot["sasTokenParameters"]["token"] = sasToken.Token.Value(); + } + + if (sasToken.UseManagedIdentity.HasValue()) + { + jsonRoot["sasTokenParameters"]["useManagedIdentity"] = sasToken.UseManagedIdentity.Value(); + } + + jsonRoot["folder"] = folderToRestore; + + jsonBody = jsonRoot.dump(); + } + + Core::IO::MemoryBodyStream requestBody( + reinterpret_cast(jsonBody.data()), jsonBody.length()); + + Core::Http::Request request(Core::Http::HttpMethod::Put, url, &requestBody); + + request.SetHeader("Content-Type", "application/json"); + request.SetHeader("Content-Length", std::to_string(requestBody.Length())); + + auto rawResponse = m_pipeline->Send(request, context); + auto const httpStatusCode = rawResponse->GetStatusCode(); + + if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) + { + throw Core::RequestFailedException(rawResponse); + } + + BackupOperationStatus response = DeserializeBackupOperationStatus(*rawResponse); + BackupOperation operation(std::make_shared(*this), std::move(response), false); + return Response(std::move(operation), std::move(rawResponse)); +} + +BackupOperationStatus BackupClient::DeserializeBackupOperationStatus( + Azure::Core::Http::RawResponse const& rawResponse) +{ + BackupOperationStatus response{}; + auto const& responseBody = rawResponse.GetBody(); + if (responseBody.size() > 0) + { + auto const jsonRoot + = Core::Json::_internal::json::parse(responseBody.begin(), responseBody.end()); + + response.Status = jsonRoot["status"].get(); + + if (jsonRoot.contains("statusDetails") && !jsonRoot["statusDetails"].is_null()) + { + response.StatusDetails = jsonRoot["statusDetails"].get(); + } + + response.StartTime = Core::_internal::PosixTimeConverter::PosixTimeToDateTime( + jsonRoot["startTime"].is_string() ? std::stoll(jsonRoot["startTime"].get()) + : jsonRoot["startTime"].get()); + + if (jsonRoot.contains("endTime") && !jsonRoot["endTime"].is_null()) + { + response.EndTime = Core::_internal::PosixTimeConverter::PosixTimeToDateTime( + jsonRoot["endTime"].is_string() ? std::stoll(jsonRoot["endTime"].get()) + : jsonRoot["endTime"].get()); + } + + response.JobId = jsonRoot["jobId"].get(); + + if (jsonRoot.contains("azureStorageBlobContainerUri") + && !jsonRoot["azureStorageBlobContainerUri"].is_null()) + { + response.AzureStorageBlobContainerUri + = jsonRoot["azureStorageBlobContainerUri"].get(); + } + + if (jsonRoot.contains("error") && !jsonRoot["error"].is_null()) + { + response.Error = DeserializeKeyVaultServiceError(jsonRoot["error"]); + } + } + return response; +} + +KeyVaultServiceError BackupClient::DeserializeKeyVaultServiceError( + Azure::Core::Json::_internal::json errorFragment) +{ + KeyVaultServiceError result; + if (errorFragment.contains("code")) + { + result.Code = errorFragment["code"].get(); + } + if (errorFragment.contains("message")) + { + result.Message = errorFragment["message"].get(); + } + return result; +} diff --git a/sdk/keyvault/azure-security-keyvault-administration/src/backup_operation.cpp b/sdk/keyvault/azure-security-keyvault-administration/src/backup_operation.cpp new file mode 100644 index 000000000..043c960a7 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/src/backup_operation.cpp @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +#include "azure/keyvault/administration/backup_operation.hpp" + +#include "azure/keyvault/administration/backup_client.hpp" + +using namespace Azure::Security::KeyVault::Administration; + +std::unique_ptr BackupOperation::PollInternal( + Azure::Core::Context const& context) +{ + std::unique_ptr rawResponse; + try + { + Azure::Response response = m_isBackupOperation + ? m_backupClient->FullBackupStatus(m_continuationToken, context) + : m_backupClient->RestoreStatus(m_continuationToken, context); + + m_value = response.Value; + m_continuationToken = response.Value.JobId; + rawResponse = std::move(response.RawResponse); + if (response.Value.Status == "InProgress") + { + m_status = Azure::Core::OperationStatus::Running; + } + else if (response.Value.Status == "Succeeded") + { + m_status = Azure::Core::OperationStatus::Succeeded; + } + else if (response.Value.Status == "Failed") + { + m_status = Azure::Core::OperationStatus::Failed; + } + else + { + throw Azure::Core::RequestFailedException(response.RawResponse); + } + } + catch (Azure::Core::RequestFailedException& error) + { + rawResponse = std::move(error.RawResponse); + } + + return rawResponse; +} + +Azure::Response BackupOperation::PollUntilDoneInternal( + std::chrono::milliseconds period, + Azure::Core::Context& context) +{ + while (true) + { + // Poll will update the raw response. + Poll(context); + if (IsDone()) + { + break; + } + std::this_thread::sleep_for(period); + } + + return Azure::Response( + m_value, std::make_unique(*m_rawResponse)); +} diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/ut/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-administration/test/ut/CMakeLists.txt index 5ccf473e8..6410db2ba 100644 --- a/sdk/keyvault/azure-security-keyvault-administration/test/ut/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-administration/test/ut/CMakeLists.txt @@ -17,10 +17,11 @@ add_compile_definitions(AZURE_TEST_RECORDING_DIR="${CMAKE_CURRENT_LIST_DIR}") add_executable ( azure-security-keyvault-administration-test + backup_restore_client_base_test.hpp + backup_restore_client_test.cpp macro_guard.cpp settings_client_base_test.hpp - settings_client_test.cpp -) + settings_client_test.cpp) target_compile_definitions(azure-security-keyvault-administration-test PRIVATE _azure_BUILDING_TESTS) diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_base_test.hpp b/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_base_test.hpp new file mode 100644 index 000000000..848a68e34 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_base_test.hpp @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @file + * @brief The base class to construct and init a Key Vault client. + * + */ + +#include +#include +#include + +#include +#include + +#include + +namespace Azure { + namespace Security { + namespace KeyVault { + namespace Administration { + namespace Test { + + class BackupRestoreClientTest : public Azure::Core::Test::TestBase, + public ::testing::WithParamInterface { + + public: + BackupRestoreClientTest() { TestBase::SetUpTestSuiteLocal(AZURE_TEST_ASSETS_DIR); } + void CreateHSMClientForTest(std::string hsmUrl = "") + { + BackupClientOptions options; + m_client = InitTestClient< + Azure::Security::KeyVault::Administration::BackupClient, + Azure::Security::KeyVault::Administration::Models::BackupClientOptions>( + hsmUrl.length() == 0 ? m_keyVaultHsmUrl : hsmUrl, m_credential, options); + } + + SasTokenParameter GetSasTokenBackup(bool managedIdentity = false) + { + SasTokenParameter sasTokenParameter; + // the backup/restore needs a SAS token to access the storage account + sasTokenParameter.Token = GetEnv("AZURE_KEYVAULT_BACKUP_TOKEN"); + // the backup/restore needs a url to a blob storage resource + m_blobUrl = Azure::Core::Url(GetEnv("AZURE_KEYVAULT_BACKUP_URL")); + + sasTokenParameter.UseManagedIdentity = managedIdentity; + return sasTokenParameter; + } + + private: + std::unique_ptr m_client; + + protected: + std::shared_ptr m_credential; + std::string m_keyVaultUrl; + std::string m_keyVaultHsmUrl; + Azure::Core::Url m_blobUrl; + std::chrono::milliseconds m_testPollingIntervalMs = std::chrono::seconds(1); + + // Reads the current test instance name. + // Name gets also sanitized (special chars are removed) to avoid issues when recording or + // creating. This also return the name with suffix if the "AZURE_LIVE_TEST_SUFFIX" exists. + std::string GetTestName(bool sanitize = true) + { + auto output = m_keyVaultUrl.compare(m_keyVaultHsmUrl) == 0 ? "Same" : "NotSame"; + std::cout << "\n Keyvault and HSM are" << output; + return Azure::Core::Test::TestBase::GetTestNameSuffix(sanitize); + } + + Azure::Security::KeyVault::Administration::BackupClient& GetClientForTest( + std::string const& testName) + { + // set the interceptor for the current test + m_testContext.RenameTest(testName); + return *m_client; + } + + // Create + virtual void SetUp() override + { + Azure::Core::Test::TestBase::SetUpTestBase(AZURE_TEST_RECORDING_DIR); + m_keyVaultUrl = GetEnv("AZURE_KEYVAULT_URL"); + m_keyVaultHsmUrl = GetEnv("AZURE_KEYVAULT_HSM_URL"); + + // Options and credential for the client + BackupClientOptions options; + m_credential = GetTestCredential(); + + // `InitTestClient` takes care of setting up Record&Playback. + m_client = InitTestClient< + Azure::Security::KeyVault::Administration::BackupClient, + Azure::Security::KeyVault::Administration::Models::BackupClientOptions>( + m_keyVaultUrl, m_credential, options); + + UpdateWaitingTime(m_testPollingIntervalMs); + } + + public: + template + static inline void CheckValidResponse( + Azure::Response& response, + Azure::Core::Http::HttpStatusCode expectedCode = Azure::Core::Http::HttpStatusCode::Ok) + { + auto const& rawResponse = response.RawResponse; + EXPECT_EQ( + static_cast::type>( + rawResponse->GetStatusCode()), + static_cast::type>( + expectedCode)); + } + + static inline std::string GetUniqueName() { return Azure::Core::Uuid::CreateUuid().ToString(); } + }; +}}}}} // namespace Azure::Security::KeyVault::Administration::Test diff --git a/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_test.cpp b/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_test.cpp new file mode 100644 index 000000000..6fc130b26 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-administration/test/ut/backup_restore_client_test.cpp @@ -0,0 +1,310 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "azure/keyvault/administration/backup_client.hpp" +#include "backup_restore_client_base_test.hpp" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Azure::Security::KeyVault::Administration; +using namespace Azure::Security::KeyVault::Administration::Models; +using namespace Azure::Security::KeyVault::Administration::Test; + +using namespace std::chrono_literals; + +TEST_F(BackupRestoreClientTest, BackupFull_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "BackupFull"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, BackupFullStatus_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "BackupFullStatus"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + auto response2 = response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response2.Value.Status, "Succeeded"); + EXPECT_TRUE(response2.Value.EndTime.Value() > response2.Value.StartTime); + EXPECT_FALSE(response2.Value.Error.HasValue()); + EXPECT_EQ(response.Value().JobId, response2.Value.JobId); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, BackupFullStatusEmptyJobId_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "BackupFullStatusEmptyJobId"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + try + { + auto response2 = client.FullBackupStatus().Value; + } + catch (Azure::Core::RequestFailedException& e) + { + EXPECT_EQ(e.RawResponse->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NotFound); + } + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, RestoreStatusEmptyJobId_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "RestoreStatusEmptyJobId"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + try + { + auto response2 = client.RestoreStatus().Value; + } + catch (Azure::Core::RequestFailedException& e) + { + EXPECT_EQ(e.RawResponse->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NotFound); + } + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, BackupFullErrorStatus_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "BackupFullErrorStatus"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + Azure::Core::Url defectiveUrl( + m_blobUrl.GetScheme() + "://" + m_blobUrl.GetHost()); // invalid uri + auto response = client.FullBackup(defectiveUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + + response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response.Value().Status, "Failed"); + EXPECT_TRUE(response.Value().EndTime.Value() > response.Value().StartTime); + EXPECT_EQ(response.Value().StatusDetails.Value(), "InvalidQueryParameterValue"); + EXPECT_EQ(response.Value().Error.Value().Code, "InvalidQueryParameterValue"); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, RestoreFull_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "RestoreFull"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + auto response2 = response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response2.Value.Status, "Succeeded"); + EXPECT_TRUE(response2.Value.EndTime.Value() > response2.Value.StartTime); + EXPECT_FALSE(response2.Value.Error.HasValue()); + EXPECT_EQ(response.Value().JobId, response2.Value.JobId); + + Azure::Core::Url url(response2.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + auto response3 = client.FullRestore(m_blobUrl, folderToRestore, sasTokenParameter).Value; + EXPECT_EQ(response3.Value().Status, "InProgress"); + EXPECT_TRUE(response3.Value().StartTime > response3.Value().StartTime.min()); + EXPECT_FALSE(response3.Value().EndTime.HasValue()); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, RestoreFullStatus_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "RestoreFullStatus"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + auto response2 = response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response2.Value.Status, "Succeeded"); + EXPECT_TRUE(response2.Value.EndTime.Value() > response2.Value.StartTime); + EXPECT_FALSE(response2.Value.Error.HasValue()); + EXPECT_EQ(response.Value().JobId, response2.Value.JobId); + + Azure::Core::Url url(response2.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + auto response3 = client.FullRestore(m_blobUrl, folderToRestore, sasTokenParameter).Value; + EXPECT_EQ(response3.Value().Status, "InProgress"); + EXPECT_TRUE(response3.Value().StartTime > response3.Value().StartTime.min()); + EXPECT_FALSE(response3.Value().EndTime.HasValue()); + auto response4 = response3.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response4.Value.Status, "Succeeded"); + EXPECT_TRUE(response4.Value.EndTime.Value() > response4.Value.StartTime); + EXPECT_FALSE(response4.Value.Error.HasValue()); + EXPECT_EQ(response3.Value().JobId, response4.Value.JobId); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, RestoreSelectiveStatus_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "RestoreSelectiveStatus"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + auto response2 = response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response2.Value.Status, "Succeeded"); + EXPECT_TRUE(response2.Value.EndTime.Value() > response2.Value.StartTime); + EXPECT_FALSE(response2.Value.Error.HasValue()); + EXPECT_EQ(response.Value().JobId, response2.Value.JobId); + + Azure::Core::Url url(response2.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + auto response3 + = client.SelectiveKeyRestore("trytry", m_blobUrl, folderToRestore, sasTokenParameter).Value; + EXPECT_EQ(response3.Value().Status, "InProgress"); + EXPECT_TRUE(response3.Value().StartTime > response3.Value().StartTime.min()); + EXPECT_FALSE(response3.Value().EndTime.HasValue()); + auto response4 = response3.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response4.Value.Status, "Succeeded"); + EXPECT_TRUE(response4.Value.EndTime.Value() > response4.Value.StartTime); + EXPECT_FALSE(response4.Value.Error.HasValue()); + EXPECT_EQ(response3.Value().JobId, response4.Value.JobId); + } + else + { + SkipTest(); + } +} + +TEST_F(BackupRestoreClientTest, RestoreSelectiveInvalidKeyStatus_RECORDEDONLY_) +{ + if (m_keyVaultHsmUrl != m_keyVaultUrl) + { + auto testName = "RestoreSelectiveInvalidKeyStatus"; + CreateHSMClientForTest(); + auto& client = GetClientForTest(testName); + SasTokenParameter sasTokenParameter = GetSasTokenBackup(); + + auto response = client.FullBackup(m_blobUrl, sasTokenParameter).Value; + + EXPECT_EQ(response.Value().Status, "InProgress"); + EXPECT_TRUE(response.Value().StartTime > response.Value().StartTime.min()); + EXPECT_FALSE(response.Value().EndTime.HasValue()); + EXPECT_FALSE(response.Value().Error.HasValue()); + auto response2 = response.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response2.Value.Status, "Succeeded"); + EXPECT_TRUE(response2.Value.EndTime.Value() > response2.Value.StartTime); + EXPECT_FALSE(response2.Value.Error.HasValue()); + EXPECT_EQ(response.Value().JobId, response2.Value.JobId); + + Azure::Core::Url url(response2.Value.AzureStorageBlobContainerUri); + auto subPath = url.GetPath(); + std::string folderToRestore = subPath.substr(7, subPath.size() - 1); + + auto response3 + = client.SelectiveKeyRestore("trytry2", m_blobUrl, folderToRestore, sasTokenParameter) + .Value; + EXPECT_EQ(response3.Value().Status, "InProgress"); + EXPECT_TRUE(response3.Value().StartTime > response3.Value().StartTime.min()); + EXPECT_FALSE(response3.Value().EndTime.HasValue()); + auto response4 = response3.PollUntilDone(m_testPollingIntervalMs); + EXPECT_EQ(response4.Value.Status, "Failed"); + EXPECT_TRUE(response4.Value.EndTime.Value() > response4.Value.StartTime); + EXPECT_EQ(response4.Value.StatusDetails.Value(), "The given key or its versions NOT found"); + EXPECT_EQ(response3.Value().JobId, response4.Value.JobId); + EXPECT_EQ(response4.Value.Error.Value().Message, "The given key or its versions NOT found"); + EXPECT_EQ(response4.Value.Error.Value().Code, "No key versions are updated"); + } + else + { + SkipTest(); + } +} diff --git a/sdk/keyvault/ci.yml b/sdk/keyvault/ci.yml index c491e25ee..baf9eeb6a 100644 --- a/sdk/keyvault/ci.yml +++ b/sdk/keyvault/ci.yml @@ -77,6 +77,10 @@ extends: Value: "debug" - Name: LOGGING__LOGLEVEL__MICROSOFT Value: "debug" + - Name: AZURE_KEYVAULT_BACKUP_TOKEN + Value: "Sanitized" + - Name: AZURE_KEYVAULT_BACKUP_URL + Value: "https://non-real-account.blob.core.windows.net/backup" CMakeTestOptions: - Name: Default Value: ''