GetSecret Api implementation for Keyvault Secret (#2656)

* Progress stream reader

* format

* Update sdk/core/azure-core/src/io/body_stream.cpp

Co-authored-by: JinmingHu <jinmhu@microsoft.com>

* PR comments

* remove

* one more comment

* replaced if null with azure_assert

* moved from pointer to reference

* first pass

* src builds

* new line

* huzaaaaa

* readme

* strating point

* get progress, need to deserialize now

* serializer

* some more

* tests

* more tests

* some refactor

* start

* comment and formatting

* working again

* couple of updates

* clang

* remove pedantic ;

* include

* pr comments

* clang format

* trigger

* trigger 2

* PR comments

* name value swap

Co-authored-by: JinmingHu <jinmhu@microsoft.com>
This commit is contained in:
George Arama 2021-07-23 17:36:04 -07:00 committed by GitHub
parent 4805900fb6
commit 0d4f138cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1126 additions and 13 deletions

View File

@ -15,6 +15,7 @@
#include "curl_connection_private.hpp"
#include <atomic>
#include <azure/core/http/curl_transport.hpp>
#include <condition_variable>
#include <curl/curl.h>
#include <list>

View File

@ -20,9 +20,9 @@ include(AzureGlobalCompileOptions)
az_vcpkg_integrate()
if(NOT AZ_ALL_LIBRARIES)
find_package(azure-core-cpp "1.0.0" CONFIG QUIET)
find_package(azure-core-cpp "1.1.0" CONFIG QUIET)
if(NOT azure-core-cpp_FOUND)
find_package(azure-core-cpp "1.0.0" REQUIRED)
find_package(azure-core-cpp "1.1.0" REQUIRED)
endif()
endif()
@ -30,13 +30,19 @@ set(
AZURE_SECURITY_KEYVAULT_SECRETS_HEADER
inc/azure/keyvault/secrets/dll_import_export.hpp
inc/azure/keyvault/secrets/secret_client.hpp
inc/azure/keyvault/secrets/keyvault_secret.hpp
inc/azure/keyvault/keyvault_secrets.hpp
)
set(
AZURE_SECURITY_KEYVAULT_SECRETS_SOURCE
src/private/package_version.hpp
src/private/keyvault_protocol.hpp
src/private/secret_constants.hpp
src/private/secret_serializers.hpp
src/keyvault_protocol.cpp
src/secret_client.cpp
src/secret_serializers.cpp
)
add_library(azure-security-keyvault-secrets ${AZURE_SECURITY_KEYVAULT_SECRETS_HEADER} ${AZURE_SECURITY_KEYVAULT_SECRETS_SOURCE})
@ -49,7 +55,7 @@ target_include_directories(
$<INSTALL_INTERFACE:include>
)
#target_link_libraries(azure-security-keyvault-secrets PUBLIC Azure::azure-security-keyvault-secrets)
target_link_libraries(azure-security-keyvault-secrets PUBLIC Azure::azure-core)
# coverage. Has no effect if BUILD_CODE_COVERAGE is OFF
create_code_coverage(keyvault-secrets azure-security-keyvault-secrets azure-security-keyvault-secrets-test)
@ -64,6 +70,7 @@ if(BUILD_TESTING)
endif()
add_subdirectory(test/ut)
add_subdirectory(test/sample)
endif()
if (BUILD_PERFORMANCE_TESTS)

View File

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Keyvault Secret definition
*/
#pragma once
#include <azure/keyvault/secrets/keyvault_secret_properties.hpp>
namespace Azure { namespace Security { namespace KeyVault { namespace Secrets {
struct KeyVaultSecret final
{
/**
* @brief The name of the secret.
*
*/
std::string Name;
/**
* @brief The secret value.
*
*/
std::string Value;
/**
* @brief The secret id.
*
*/
std::string Id;
/**
* @brief The secret Properties bundle.
*
*/
KeyvaultSecretProperties Properties;
/**
* @brief Construct a new KeyVaultSecret object.
*
*/
KeyVaultSecret() = default;
/**
* @brief Construct a new KeyVaultSecret object.
*
* @param name The name of the secret.
* @param value The name of the secret.
*/
KeyVaultSecret(std::string name, std::string value)
: Name(std::move(name)), Value(std::move(value))
{
if (Name.empty())
{
throw std::invalid_argument("Name cannot be empty");
}
}
};
}}}} // namespace Azure::Security::KeyVault::Secrets

View File

@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Keyvault Secret Attributes definition
*/
#pragma once
#include <azure/core/datetime.hpp>
#include <azure/core/nullable.hpp>
#include <unordered_map>
namespace Azure { namespace Security { namespace KeyVault { namespace Secrets {
/**
* @brief The Secret attributes managed by the KeyVault service.
*
*/
struct KeyvaultSecretProperties final
{
/**
* @brief Indicate whether the secret is enabled and useable for cryptographic operations.
*
*/
Azure::Nullable<bool> Enabled;
/**
* @brief Indicate when the secret will be valid and can be used for cryptographic operations.
*
*/
Azure::Nullable<Azure::DateTime> NotBefore;
/**
* @brief Indicate when the secret will expire and cannot be used for cryptographic operations.
*
*/
Azure::Nullable<Azure::DateTime> ExpiresOn;
/**
* @brief Indicate when the secret was created.
*
*/
Azure::Nullable<Azure::DateTime> CreatedOn;
/**
* @brief Indicate when the secret was updated.
*
*/
Azure::Nullable<Azure::DateTime> UpdatedOn;
/**
* @brief The number of days a secret is retained before being deleted for a soft delete-enabled
* Key Vault.
*
*/
Azure::Nullable<int64_t> RecoverableDays;
/**
* @brief The recovery level currently in effect for secrets in the Key Vault.
*
* @remark If Purgeable, the secret can be permanently deleted by an authorized user; otherwise,
* only the service can purge the secret at the end of the retention interval.
*
*/
Azure::Nullable<std::string> RecoveryLevel;
/**
* @brief The content type of the secret.
*
*/
Azure::Nullable<std::string> ContentType;
/**
* @brief If this is a secret backing a KV certificate, then this field specifies the
* corresponding key backing the KV certificate.
*
*/
Azure::Nullable<std::string> KeyId;
/**
* @brief Application specific metadata in the form of key-value pairs.
*
*/
std::unordered_map<std::string, std::string> Tags;
/**
* @brief True if the secret's lifetime is managed by key vault. If this is a secret
* backing a certificate, then managed will be true.
*
*/
bool Managed = false;
/**
* @brief The secret id.
*
*/
std::string Id;
/**
* @brief The name of the secret.
*
*/
std::string Name;
/**
* @brief The vault url of the secret.
*
*/
std::string VaultUrl;
/**
* @brief The version of the secret.
*
*/
std::string Version;
/**
* @brief Construct a new secret Properties object.
*
*/
KeyvaultSecretProperties() = default;
};
}}}} // namespace Azure::Security::KeyVault::Secrets

View File

@ -1,14 +1,137 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Defines the Key Vault Secret client.
*
*/
#pragma once
#include "../src/private/keyvault_protocol.hpp"
#include "dll_import_export.hpp"
#include <azure/keyvault/secrets/keyvault_secret.hpp>
#include <string>
namespace Azure { namespace Security { namespace KeyVault { namespace Secrets {
class SecretClient final {
class ServiceVersion final {
private:
std::string m_version;
public:
std::string ClientVersion() const;
/**
* @brief Construct a new Service Version object
*
* @param version The string version for the Key Vault keys service.
*/
ServiceVersion(std::string version) : m_version(std::move(version)) {}
/**
* @brief Enable comparing the ext enum.
*
* @param other Another #ServiceVersion to be compared.
*/
bool operator==(ServiceVersion const& other) const { return m_version == other.m_version; }
/**
* @brief Return the #ServiceVersion string representation.
*
*/
std::string const& ToString() const { return m_version; }
/**
* @brief Use to send request to the 7.2 version of Key Vault service.
*
*/
AZURE_SECURITY_KEYVAULT_SECRETS_DLLEXPORT static const ServiceVersion V7_2;
};
/**
* @brief Define the options to create an SDK Keys client.
*
*/
struct SecretClientOptions final : public Azure::Core::_internal::ClientOptions
{
ServiceVersion Version;
/**
* @brief Construct a new Secret Client Options object.
*
* @param version Optional version for the client.
*/
SecretClientOptions(ServiceVersion version = ServiceVersion::V7_2)
: Azure::Core::_internal::ClientOptions(), Version(version)
{
}
};
/**
* @brief Optional parameters for SecretClient::GetSecret
*
*/
struct GetSecretOptions final
{
/**
* @brief Specify the key version to get.
*
*/
std::string Version;
};
/**
* @brief The SecretClient provides synchronous methods to manage a secret in the Azure Key
* Vault. The client supports creating, retrieving, updating, deleting, purging, backing up,
* restoring, and listing the secret.
*/
class SecretClient
#if !defined(TESTING_BUILD)
final
#endif
{
protected:
// Using a shared pipeline for a client to share it with LRO (like delete key)
std::shared_ptr<Azure::Security::KeyVault::_detail::KeyVaultProtocolClient> m_protocolClient;
public:
/**
* @brief Construct a new SecretClient 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 SecretClient(
std::string const& vaultUrl,
std::shared_ptr<Azure::Core::Credentials::TokenCredential const> credential,
SecretClientOptions options = SecretClientOptions());
/**
* @brief Construct a new Key Client object from another key client.
*
* @param keyClient An existing key vault key client.
*/
explicit SecretClient(SecretClient const& keyClient)
: m_protocolClient(keyClient.m_protocolClient){};
~SecretClient() = default;
/**
* @brief Get a specified secret from a given key vault
* This operation is applicable to any secret stored in Azure Key Vault.
* This operation requires the secrets/get permission.
*
* @param name The name of the secret<span class="x x-first x-last">.</span>
* @param options The optional parameters for this request.
*
* @param context The context for the operation can be used for request cancellation.
* @return The Secret wrapped in the Response.
*/
Azure::Response<KeyVaultSecret> GetSecret(
std::string const& name,
GetSecretOptions const& options = GetSecretOptions(),
Azure::Core::Context const& context = Azure::Core::Context()) const;
};
}}}} // namespace Azure::Security::KeyVault::Secrets

View File

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "private/keyvault_protocol.hpp"
#include "private/secret_constants.hpp"
#include <azure/core/exception.hpp>
#include <azure/core/http/http.hpp>
using namespace Azure::Security::KeyVault;
using namespace Azure::Core::Http::_internal;
Azure::Core::Http::Request _detail::KeyVaultProtocolClient::CreateRequest(
Azure::Core::Http::HttpMethod method,
Azure::Core::IO::BodyStream* content,
std::vector<std::string> const& path) const
{
Azure::Core::Http::Request request = content == nullptr
? Azure::Core::Http::Request(method, m_vaultUrl)
: Azure::Core::Http::Request(method, m_vaultUrl, content);
request.SetHeader(HttpShared::ContentType, HttpShared::ApplicationJson);
request.SetHeader(HttpShared::Accept, HttpShared::ApplicationJson);
request.GetUrl().AppendQueryParameter(
Azure::Security::KeyVault::Secrets::_detail::ApiVersion, m_apiVersion);
for (std::string const& p : path)
{
if (!p.empty())
{
request.GetUrl().AppendPath(p);
}
}
return request;
}
Azure::Core::Http::Request _detail::KeyVaultProtocolClient::CreateRequest(
Azure::Core::Http::HttpMethod method,
std::vector<std::string> const& path) const
{
return CreateRequest(method, nullptr, path);
}
std::unique_ptr<Azure::Core::Http::RawResponse> _detail::KeyVaultProtocolClient::SendRequest(
Azure::Core::Context const& context,
Azure::Core::Http::Request& request) const
{
auto response = m_pipeline.Send(request, context);
auto responseCode = response->GetStatusCode();
switch (responseCode)
{
// 200, 2001, 202, 204 are accepted responses
case Azure::Core::Http::HttpStatusCode::Ok:
case Azure::Core::Http::HttpStatusCode::Created:
case Azure::Core::Http::HttpStatusCode::Accepted:
case Azure::Core::Http::HttpStatusCode::NoContent:
break;
default:
throw Azure::Core::RequestFailedException(response);
}
return response;
}

View File

@ -0,0 +1,189 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Provides a wrapper class for the Azure Core Pipeline for all Key Vault services where
* common functionality is set up.
*
*/
#pragma once
#include <azure/core/http/http.hpp>
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/internal/json/json.hpp>
#include <azure/core/internal/json/json_serializable.hpp>
#include <azure/core/response.hpp>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace Azure { namespace Security { namespace KeyVault { namespace _detail {
/**
* @brief The Protocol layer used by Key Vault clients.
*
*/
class KeyVaultProtocolClient final {
Azure::Core::Url m_vaultUrl;
Azure::Core::Http::_internal::HttpPipeline m_pipeline;
std::string m_apiVersion;
/**
* @brief Create a Request to be sent.
*
* @param method Represent an HTTP method.
* @param path The path for the HTTP request.
* @return A constructed request.
*/
Azure::Core::Http::Request CreateRequest(
Azure::Core::Http::HttpMethod method,
std::vector<std::string> const& path) const;
/**
* @brief Create a Key Vault request with payload.
*
* @param method The HTTP method.
* @param content The HTTP payload.
* @param path The HTTP request path.
* @return A constructed request.
*/
Azure::Core::Http::Request CreateRequest(
Azure::Core::Http::HttpMethod method,
Azure::Core::IO::BodyStream* content,
std::vector<std::string> const& path) const;
/**
* @brief Start the HTTP transfer based on the \p request.
*
* @param context The context for per-operation options or cancellation.
* @param request The HTTP request to be sent.
* @return The raw response from the network.
*/
std::unique_ptr<Azure::Core::Http::RawResponse> SendRequest(
Azure::Core::Context const& context,
Azure::Core::Http::Request& request) const;
public:
/**
* @brief Construct a new Key Vault Protocol Client.
*
* @param vaultUrl The URL address for the Key Vault.
* @param apiVersion The service API version.
* @param pipeline The HTTP pipeline for sending requests with.
*/
explicit KeyVaultProtocolClient(
Azure::Core::Url vaultUrl,
std::string apiVersion,
Azure::Core::Http::_internal::HttpPipeline&& pipeline)
: m_vaultUrl(std::move(vaultUrl)), m_pipeline(pipeline), m_apiVersion(std::move(apiVersion))
{
}
/**
* @brief Create and send the HTTP request. Uses the \p factoryFn function to create
* the response type.
*
* @param context The context for per-operation options or cancellation.
* @param method The method for the request.
* @param factoryFn The function to deserialize and produce T from the raw response.
* @param path A path for the request represented as a vector of strings.
* @param query Optional query parameters for constructing the request.
* @return The object produced by the \p factoryFn and the raw response from the network.
*/
template <class T>
Azure::Response<T> SendRequest(
Azure::Core::Context const& context,
Azure::Core::Http::HttpMethod method,
std::function<T(Azure::Core::Http::RawResponse const& rawResponse)> factoryFn,
std::vector<std::string> const& path,
std::unique_ptr<std::map<std::string, std::string>> const& query = nullptr)
{
auto request = CreateRequest(method, path);
if (query != nullptr)
{
for (auto const& queryParameter : *query)
{
request.GetUrl().AppendQueryParameter(queryParameter.first, queryParameter.second);
}
}
auto response = SendRequest(context, request);
// Saving the value in a local is required before passing it in to Response<T> to avoid
// compiler optimizations re-ordering the `factoryFn` function call and the RawResponse move.
T value = factoryFn(*response);
return Azure::Response<T>(std::move(value), std::move(response));
}
/**
* @brief Create and send the HTTP request with payload content. Uses the \p factoryFn function
* to create the response type.
*
* @param context The context for per-operation options or cancellation.
* @param method The method for the request.
* @param content The HTTP payload.
* @param factoryFn The function to deserialize and produce T from the raw response.
* @param path A path for the request represented as a vector of strings.
* @return The object produced by the \p factoryFn and the raw response from the network.
*/
template <class T>
Azure::Response<T> SendRequest(
Azure::Core::Context const& context,
Azure::Core::Http::HttpMethod method,
Azure::Core::Json::_internal::JsonSerializable const& content,
std::function<T(Azure::Core::Http::RawResponse const& rawResponse)> factoryFn,
std::vector<std::string> const& path)
{
auto serialContent = content.Serialize();
auto streamContent = Azure::Core::IO::MemoryBodyStream(
reinterpret_cast<const uint8_t*>(serialContent.data()), serialContent.size());
auto request = CreateRequest(method, &streamContent, path);
auto response = SendRequest(context, request);
// Saving the value in a local is required before passing it in to Response<T> to avoid
// compiler optimizations re-ordering the `factoryFn` function call and the RawResponse move.
T value = factoryFn(*response);
return Azure::Response<T>(value, std::move(response));
}
template <class T>
Azure::Response<T> SendRequest(
Azure::Core::Context const& context,
Azure::Core::Http::HttpMethod method,
std::function<std::string()> serializeContentFn,
std::function<T(Azure::Core::Http::RawResponse const& rawResponse)> factoryFn,
std::vector<std::string> const& path)
{
auto serialContent = serializeContentFn();
auto streamContent = Azure::Core::IO::MemoryBodyStream(
reinterpret_cast<const uint8_t*>(serialContent.data()), serialContent.size());
auto request = CreateRequest(method, &streamContent, path);
auto response = SendRequest(context, request);
// Saving the value in a local is required before passing it in to Response<T> to avoid
// compiler optimizations re-ordering the `factoryFn` function call and the RawResponse move.
T value = factoryFn(*response);
return Azure::Response<T>(value, std::move(response));
}
/**
* @brief Create a key vault request and send it using the Azure Core pipeline directly to avoid
* checking the respone code.
*
* @param context A context for cancellation.
* @param method The HTTP method for the request.
* @param path The path for the request.
* @return A unique ptr to an HTTP raw response.
*/
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Context const& context,
Azure::Core::Http::HttpMethod method,
std::vector<std::string> const& path)
{
auto request = CreateRequest(method, path);
// Use the core pipeline directly to avoid checking the response code.
return m_pipeline.Send(request, context);
}
};
}}}} // namespace Azure::Security::KeyVault::_detail

View File

@ -0,0 +1,36 @@
// Copyright(c) Microsoft Corporation.All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Centralize the string constants used by Key Vault Secret Client.
*
*/
#pragma once
namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { namespace _detail {
/***************** KeyVault Secret *****************/
constexpr static const char SecretPath[] = "secrets";
/******************* Secret property names ***********/
constexpr static const char AttributesPropertyName[] = "attributes";
constexpr static const char EnabledPropertyName[] = "enabled";
constexpr static const char NbfPropertyName[] = "nbf";
constexpr static const char ExpPropertyName[] = "exp";
constexpr static const char CreatedPropertyName[] = "created";
constexpr static const char UpdatedPropertyName[] = "updated";
constexpr static const char ManagedPropertyName[] = "managed";
constexpr static const char TagsPropertyName[] = "tags";
constexpr static const char IdPropertyName[] = "id";
constexpr static const char KeyIdPropertyName[] = "kid";
constexpr static const char ValuePropertyName[] = "value";
constexpr static const char RecoveryLevelPropertyName[] = "recoveryLevel";
constexpr static const char ContentTypePropertyName[] = "contentType";
constexpr static const char RecoverableDaysPropertyName[] = "recoverableDays";
/**************** KeyVault QueryParameters *********/
static constexpr char const ApiVersion[] = "api-version";
}}}}} // namespace Azure::Security::KeyVault::Secrets::_detail

View File

@ -0,0 +1,79 @@
// Copyright(c) Microsoft Corporation.All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Serializers/sdeserializers for the KeyVault Secret client.
*
*/
#pragma once
#include <azure/core/http/http.hpp>
#include <azure/core/internal/json/json.hpp>
#include <azure/keyvault/secrets/keyvault_secret.hpp>
using namespace Azure::Security::KeyVault::Secrets;
namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { namespace _detail {
struct KeyVaultSecretSerializer final
{
// Creates a new key based on a name and an HTTP raw response.
static KeyVaultSecret KeyVaultSecretDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse);
// Create from HTTP raw response only.
static KeyVaultSecret KeyVaultSecretDeserialize(
Azure::Core::Http::RawResponse const& rawResponse);
// Updates a Key based on an HTTP raw response.
static void KeyVaultSecretDeserialize(
KeyVaultSecret& key,
Azure::Core::Http::RawResponse const& rawResponse);
// extract the host out of the URL (with port if available)
static std::string GetUrlAuthorityWithScheme(Azure::Core::Url const& url)
{
std::string urlString;
if (!url.GetScheme().empty())
{
urlString += url.GetScheme() + "://";
}
urlString += url.GetHost();
if (url.GetPort() != 0)
{
urlString += ":" + std::to_string(url.GetPort());
}
return urlString;
}
// parse the ID url to extract relevant data
void static inline ParseIDUrl(
KeyvaultSecretProperties& secretProperties,
std::string const& url)
{
Azure::Core::Url sid(url);
secretProperties.Id = url;
secretProperties.VaultUrl = GetUrlAuthorityWithScheme(sid);
auto const& path = sid.GetPath();
// path is in the form of `verb/keyName{/keyVersion}`
auto const separatorChar = '/';
auto pathEnd = path.end();
auto start = path.begin();
start = std::find(start, pathEnd, separatorChar);
start += 1;
auto separator = std::find(start, pathEnd, separatorChar);
if (separator != pathEnd)
{
secretProperties.Name = std::string(start, separator);
start = separator + 1;
secretProperties.Version = std::string(start, pathEnd);
}
else
{
// Nothing but the name+
secretProperties.Name = std::string(start, pathEnd);
}
}
};
}}}}} // namespace Azure::Security::KeyVault::Secrets::_detail

View File

@ -5,13 +5,63 @@
* @brief Keyvault Secrets Client definition.
*
*/
#include "azure/keyvault/secrets/secret_client.hpp"
#include "private/keyvault_protocol.hpp"
#include "private/package_version.hpp"
#include "private/secret_constants.hpp"
#include "private/secret_serializers.hpp"
#include <azure/core/credentials/credentials.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/http/policies/policy.hpp>
#include <string>
using namespace Azure::Security::KeyVault::Secrets;
using namespace Azure::Security::KeyVault::Secrets::_detail;
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http::Policies::_internal;
std::string SecretClient::ClientVersion() const { return PackageVersion::ToString(); }
namespace {
constexpr static const char TelemetryName[] = "keyvault-secrets";
}
SecretClient::SecretClient(
std::string const& vaultUrl,
std::shared_ptr<Core::Credentials::TokenCredential const> credential,
SecretClientOptions options)
{
auto apiVersion = options.Version.ToString();
std::vector<std::unique_ptr<HttpPolicy>> perRetrypolicies;
{
Azure::Core::Credentials::TokenRequestContext const tokenContext
= {{"https://vault.azure.net/.default"}};
perRetrypolicies.emplace_back(
std::make_unique<BearerTokenAuthenticationPolicy>(credential, tokenContext));
}
m_protocolClient = std::make_shared<Azure::Security::KeyVault::_detail::KeyVaultProtocolClient>(
Azure::Core::Url(vaultUrl),
apiVersion,
Azure::Core::Http::_internal::HttpPipeline(
options, TelemetryName, apiVersion, std::move(perRetrypolicies), {}));
}
Azure::Response<KeyVaultSecret> SecretClient::GetSecret(
std::string const& name,
GetSecretOptions const& options,
Azure::Core::Context const& context) const
{
return m_protocolClient->SendRequest<KeyVaultSecret>(
context,
Azure::Core::Http::HttpMethod::Get,
[&name](Azure::Core::Http::RawResponse const& rawResponse) {
return _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(name, rawResponse);
},
{_detail::SecretPath, name, options.Version});
}
const ServiceVersion ServiceVersion::V7_2("7.2");

View File

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Keyvault Secret serializers
*/
#include "private/secret_serializers.hpp"
#include "private/secret_constants.hpp"
#include <azure/core/internal/json/json.hpp>
#include <azure/core/internal/json/json_optional.hpp>
#include <azure/core/internal/json/json_serializable.hpp>
using namespace Azure::Core::Json::_internal;
using Azure::Core::_internal::PosixTimeConverter;
using namespace Azure::Security::KeyVault::Secrets;
using namespace Azure::Security::KeyVault::Secrets::_detail;
// Creates a new key based on a name and an HTTP raw response.
KeyVaultSecret KeyVaultSecretSerializer::KeyVaultSecretDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse)
{
KeyVaultSecret secret(name, "");
_detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, rawResponse);
return secret;
}
// Create from HTTP raw response only.
KeyVaultSecret KeyVaultSecretSerializer::KeyVaultSecretDeserialize(
Azure::Core::Http::RawResponse const& rawResponse)
{
KeyVaultSecret secret;
_detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, rawResponse);
return secret;
}
// Updates a Key based on an HTTP raw response.
void KeyVaultSecretSerializer::KeyVaultSecretDeserialize(
KeyVaultSecret& secret,
Azure::Core::Http::RawResponse const& rawResponse)
{
auto const& body = rawResponse.GetBody();
auto jsonParser = json::parse(body);
secret.Value = jsonParser[_detail::ValuePropertyName];
secret.Id = jsonParser[_detail::IdPropertyName];
secret.Properties.Id = secret.Id;
ParseIDUrl(secret.Properties, secret.Id);
secret.Name = secret.Properties.Name;
// Parse URL for the various attributes
if (jsonParser.contains(_detail::AttributesPropertyName))
{
auto attributes = jsonParser[_detail::AttributesPropertyName];
JsonOptional::SetIfExists(secret.Properties.Enabled, attributes, _detail::EnabledPropertyName);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
secret.Properties.NotBefore,
attributes,
_detail::NbfPropertyName,
PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
secret.Properties.ExpiresOn,
attributes,
_detail::ExpPropertyName,
PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
secret.Properties.CreatedOn,
attributes,
_detail::CreatedPropertyName,
PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<int64_t, Azure::DateTime>(
secret.Properties.UpdatedOn,
attributes,
_detail::UpdatedPropertyName,
PosixTimeConverter::PosixTimeToDateTime);
JsonOptional::SetIfExists<std::string>(
secret.Properties.RecoveryLevel, attributes, _detail::RecoveryLevelPropertyName);
JsonOptional::SetIfExists<int64_t>(
secret.Properties.RecoverableDays, attributes, _detail::RecoverableDaysPropertyName);
}
// "Tags"
if (jsonParser.contains(_detail::TagsPropertyName))
{
auto const& tags = jsonParser[_detail::TagsPropertyName];
{
for (auto tag = tags.begin(); tag != tags.end(); ++tag)
{
secret.Properties.Tags.emplace(tag.key(), tag.value().get<std::string>());
}
}
}
// managed
if (jsonParser.contains(_detail::ManagedPropertyName))
{
secret.Properties.Managed = jsonParser[_detail::ManagedPropertyName].get<bool>();
}
// key id
JsonOptional::SetIfExists<std::string>(
secret.Properties.KeyId, jsonParser, _detail::KeyIdPropertyName);
// content type
JsonOptional::SetIfExists<std::string>(
secret.Properties.ContentType, jsonParser, _detail::ContentTypePropertyName);
}

View File

@ -0,0 +1,20 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: MIT
cmake_minimum_required (VERSION 3.13)
project (keyvault-secrets-test-app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_executable (
keyvault-secrets-test-app
test_app.cpp
macro_guard.cpp
)
if (MSVC)
target_compile_options(keyvault-secrets-test-app PUBLIC /wd6326 /wd26495 /wd26812)
endif()
target_link_libraries(keyvault-secrets-test-app PRIVATE azure-security-keyvault-secrets azure-identity)

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Define `min` and `max` as function-like macros before including all public
// headers to ensure that uses of those identifiers are defended against
// expansion as function-like macros. Define `small` as an object-like macro to
// ensure that identifier isn't used at all. Windows.h is badly behaved and
// defines similar macros with these names and we want to ensure the SDK headers
// function even when a naive user includes Windows.h first.
//
#define small FAIL><TO][COMPILE)(VERY{{{LOUDLY!!!
#define max(x, y) small
#define min(x, y) small
#include "azure/keyvault/keyvault_secrets.hpp"

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <azure/identity.hpp>
#include <azure/keyvault/keyvault_secrets.hpp>
using namespace Azure::Security::KeyVault::Secrets;
int main()
{
auto tenantId = std::getenv("AZURE_TENANT_ID");
auto clientId = std::getenv("AZURE_CLIENT_ID");
auto clientSecret = std::getenv("AZURE_CLIENT_SECRET");
auto credential
= std::make_shared<Azure::Identity::ClientSecretCredential>(tenantId, clientId, clientSecret);
SecretClient secretClient(std::getenv("AZURE_KEYVAULT_URL"), credential);
// just a response, with a secret
auto response = secretClient.GetSecret("testSecret");
return 0;
}

View File

@ -11,8 +11,11 @@ include(GoogleTest)
add_executable (
azure-security-keyvault-secrets-test
macro_guard.cpp
secret_client_test.cpp
secret_get_client_deserialize_test.hpp
secret_get_client_deserialize_test.cpp
)
if (MSVC)

View File

@ -1,15 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include "azure/core/context.hpp"
#include "azure/identity/client_secret_credential.hpp"
#include "azure/keyvault/secrets/secret_client.hpp"
#include <azure/keyvault/secrets/secret_client.hpp>
#include <exception>
#include <gtest/gtest.h>
#include <memory>
using namespace Azure::Security::KeyVault::Secrets;
TEST(SecretClient, Basic)
TEST(SecretClient, InitClient)
{
SecretClient secretClient;
EXPECT_FALSE(secretClient.ClientVersion().empty());
auto credential
= std::make_shared<Azure::Identity::ClientSecretCredential>("tenantID", "AppId", "SecretId");
{
EXPECT_NO_THROW(SecretClient SecretClient("vaultUrl", credential));
}
{
SecretClientOptions options;
options.Retry.MaxRetries = 10;
EXPECT_NO_THROW(SecretClient secretClient("vaultUrl", credential, options));
}
}
TEST(SecretClient, ServiceVersion)
{
auto credential
= std::make_shared<Azure::Identity::ClientSecretCredential>("tenantID", "AppId", "SecretId");
{
// 7.2
EXPECT_NO_THROW(auto options = SecretClientOptions(ServiceVersion::V7_2);
SecretClient SecretClient("vaultUrl", credential, options);
EXPECT_EQ(options.Version.ToString(), "7.2"););
}
{
// arbitrary version
EXPECT_NO_THROW(auto options = SecretClientOptions(ServiceVersion("1.0"));
SecretClient secretClient("vaultUrl", credential, options);
EXPECT_EQ(options.Version.ToString(), "1.0"););
}
}

View File

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "secret_get_client_deserialize_test.hpp"
#include "../src/private/secret_serializers.hpp"
#include "azure/keyvault/secrets/secret_client.hpp"
using namespace Azure::Security::KeyVault::Secrets;
using namespace Azure::Security::KeyVault::Secrets::_detail;
TEST(KeyVaultSecretSerializer, GetClientDeserializePartial1)
{
auto response = getPartialResponse();
KeyVaultSecret secret = _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(response);
runPartialExpect(secret);
}
TEST(KeyVaultSecretSerializer, GetClientDeserializePartial2)
{
auto response = getPartialResponse();
KeyVaultSecret secret
= _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize("name1", response);
runPartialExpect(secret);
}
TEST(KeyVaultSecretSerializer, GetClientDeserializePartial3)
{
auto response = getPartialResponse();
KeyVaultSecret secret = KeyVaultSecret("name2", "");
_detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, response);
runPartialExpect(secret);
}
TEST(KeyVaultSecretSerializer, GetClientdeserializeFull1)
{
auto response = getFullResponse();
KeyVaultSecret secret = _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(response);
runFullExpect(secret);
}
TEST(KeyVaultSecretSerializer, GetClientdeserializeFull2)
{
auto response = getFullResponse();
KeyVaultSecret secret
= _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize("name1", response);
runFullExpect(secret);
}
TEST(KeyVaultSecretSerializer, GetClientdeserializeFull3)
{
auto response = getFullResponse();
KeyVaultSecret secret = KeyVaultSecret("name2", "");
_detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, response);
runFullExpect(secret);
}

View File

@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/http/http.hpp"
#include "azure/core/http/policies/policy.hpp"
#include "azure/keyvault/secrets/keyvault_secret.hpp"
#include <gtest/gtest.h>
#include <string>
using namespace Azure::Security::KeyVault::Secrets;
using namespace Azure::Core::Http::_internal;
namespace {
Azure::Core::Http::RawResponse getPartialResponse()
{
auto response = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK");
constexpr static const uint8_t responseBody[] = R"json({
"value": "mysecretvalue",
"id": "https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79",
"attributes": {
"enabled": true,
"created": 1493938410,
"updated": 1493938410,
"recoveryLevel": "Recoverable+Purgeable"
}
}
)json";
response.SetHeader(HttpShared::ContentType, "application/json");
response.SetHeader(HttpShared::MsRequestId, "1");
response.SetHeader(HttpShared::MsClientRequestId, "2");
response.SetBody(std::vector<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response.SetBodyStream(
std::make_unique<Azure::Core::IO::MemoryBodyStream>(responseBody, sizeof(responseBody) - 1));
return response;
}
Azure::Core::Http::RawResponse getFullResponse()
{
auto response = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK");
constexpr static const uint8_t responseBody[] = R"json({
"value": "mysecretvalue",
"id": "https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79",
"contentType" : "ct",
"kid": "kid",
"managed": true,
"attributes": {
"enabled": true,
"created": 1493938410,
"updated": 1493938410,
"recoveryLevel": "Recoverable+Purgeable"
}
}
)json";
response.SetHeader(HttpShared::ContentType, "application/json");
response.SetHeader(HttpShared::MsRequestId, "1");
response.SetHeader(HttpShared::MsClientRequestId, "2");
response.SetBody(std::vector<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response.SetBodyStream(
std::make_unique<Azure::Core::IO::MemoryBodyStream>(responseBody, sizeof(responseBody) - 1));
return response;
}
void runPartialExpect(KeyVaultSecret& secret)
{
EXPECT_EQ(secret.Name, "mysecretname");
EXPECT_EQ(secret.Value, "mysecretvalue");
EXPECT_EQ(secret.Properties.VaultUrl, "https://myvault.vault.azure.net");
EXPECT_EQ(secret.Properties.Version, "4387e9f3d6e14c459867679a90fd0f79");
EXPECT_EQ(secret.Properties.Id, secret.Id);
EXPECT_EQ(
secret.Id,
"https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79");
EXPECT_EQ(secret.Properties.KeyId.HasValue(), false);
EXPECT_EQ(secret.Properties.Enabled.Value(), true);
EXPECT_EQ(secret.Properties.Managed, false);
EXPECT_EQ(secret.Properties.UpdatedOn.HasValue(), true);
EXPECT_EQ(secret.Properties.CreatedOn.HasValue(), true);
}
void runFullExpect(KeyVaultSecret& secret)
{
EXPECT_EQ(secret.Name, "mysecretname");
EXPECT_EQ(secret.Value, "mysecretvalue");
EXPECT_EQ(secret.Properties.VaultUrl, "https://myvault.vault.azure.net");
EXPECT_EQ(secret.Properties.Version, "4387e9f3d6e14c459867679a90fd0f79");
EXPECT_EQ(secret.Properties.Id, secret.Id);
EXPECT_EQ(
secret.Id,
"https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79");
EXPECT_EQ(secret.Properties.Enabled.Value(), true);
EXPECT_EQ(secret.Properties.Managed, true);
EXPECT_EQ(secret.Properties.ContentType.Value(), "ct");
EXPECT_EQ(secret.Properties.KeyId.Value(), "kid");
EXPECT_EQ(secret.Properties.UpdatedOn.HasValue(), true);
EXPECT_EQ(secret.Properties.CreatedOn.HasValue(), true);
}
} // namespace