Fixed Live Tests for Attestation SDK. (#3366)

* Set WinHTTP_OPTION_CLIENT_CERT_CONTEXT to enable connections to attestation service

* Set serviceDirectory from environment variables

Co-authored-by: Victor Vazquez <victor.vazquez@microsoft.com>
This commit is contained in:
Larry Osterman 2022-02-24 14:48:15 -08:00 committed by GitHub
parent 94badba04d
commit ddc9eb355f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 216 additions and 36 deletions

View File

@ -149,6 +149,8 @@ jobs:
CmakeArgs: ""
AZURE_TEST_MODE: "LIVE"
AZURE_LOG_LEVEL: "verbose"
# Surface the ServiceDirectory parameter as an environment variable so tests can take advantage of it.
AZURE_SERVICE_DIRECTORY: ${{ parameters.ServiceDirectory }}
steps:
- checkout: self
@ -218,6 +220,7 @@ jobs:
# This enables to run tests and samples at the same time as different matrix configuration.
# Then unit-tests runs, samples should not run.
condition: and(succeeded(), ne(variables['RunSamples'], '1'))
- task: PublishTestResults@2
inputs:

View File

@ -207,16 +207,38 @@ clients to add, remove or enumerate the policy management certificates.
The `AttestationClientBuilder` class is used to create instances of the attestation client:
```cpp readme-sample-create-synchronous-client
std::string endpoint = std::getenv("ATTESTATION_AAD_URL");
AttestationClientOptions options;
return std::make_unique<Azure::Security::Attestation::AttestationClient>(m_endpoint, options);
```
If the attestation APIs require authentication, use the following:
```cpp readme-sample-create-synchronous-client
std::string endpoint = std::getenv("ATTESTATION_AAD_URL");
AttestationClientOptions options;
std::shared_ptr<Azure::Core::Credentials::TokenCredential> credential
= std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID"), GetEnv("AZURE_CLIENT_ID"), GetEnv("AZURE_CLIENT_SECRET"));
return std::make_unique<Azure::Security::Attestation::AttestationClient>(m_endpoint, credential, options);
```
The same pattern is used to create an `Azure::Security::Attestation::AttestationAdministrationClient`.
#### Retrieve Token Certificates
Use `listAttestationSigners` to retrieve the set of certificates, which can be used to validate the token returned from the attestation service.
Use `GetAttestationSigningCertificates` to retrieve the set of certificates, which can be used to validate the token returned from the attestation service.
Normally, this information is not required as the attestation SDK will perform the validation as a part of the interaction with the
attestation service, however the APIs are provided for completeness and to facilitate customer's independently validating
attestation results.
```cpp readme-sample-getSigningCertificates
auto attestationSigners = attestationClient->GetAttestationSigningCertificates();
// Enumerate the signers.
for (const auto& signer : attestationSigners.Value.Signers)
{
}
```
#### Attest an SGX Enclave
@ -230,22 +252,22 @@ Use the `AttestSgxEnclave` method to attest an SGX enclave.
All administrative clients are authenticated.
```cpp readme-sample-create-admin-client
AttestationAdministrationClientBuilder attestationBuilder = new AttestationAdministrationClientBuilder();
// Note that the "policy" calls require authentication.
AttestationAdministrationClient client = attestationBuilder
.endpoint(endpoint)
.credential(new DefaultAzureCredentialBuilder().build())
.buildClient();
```cpp readme-sample-create-synchronous-client
std::string endpoint = std::getenv("ATTESTATION_AAD_URL");
AttestationClientOptions options;
std::shared_ptr<Azure::Core::Credentials::TokenCredential> credential
= std::make_shared<Azure::Identity::ClientSecretCredential>(
GetEnv("AZURE_TENANT_ID"), GetEnv("AZURE_CLIENT_ID"), GetEnv("AZURE_CLIENT_SECRET"));
auto adminClient = std::make_unique<AttestationAdministrationClient>(m_endpoint, credential, options);
```
#### Retrieve current attestation policy for OpenEnclave
Use the `GetAttestationPolicy` API to retrieve the current attestation policy for a given TEE.
```java readme-sample-getCurrentPolicy
String currentPolicy = client.getAttestationPolicy(AttestationType.OPEN_ENCLAVE);
System.out.printf("Current policy for OpenEnclave is: %s\n", currentPolicy);
```cpp readme-sample-getCurrentPolicy
auto currentPolicy = adminClient->GetAttestationPolicy(AttestationType.OPEN_ENCLAVE);
std::cout << "Current policy for OpenEnclave is " << currentPolicy.Value.Body << std::endl;
```
#### Set unsigned attestation policy (AAD clients only)
@ -397,3 +419,4 @@ Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk
[cloud_shell_bash]: https://shell.azure.com/bash
![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fattestation%2Fazure-security-attestation%2FREADME.png)

View File

@ -425,6 +425,9 @@ namespace Azure { namespace Security { namespace Attestation { namespace _detail
ValidateTokenIssuer(validationOptions);
}
operator Models::AttestationToken<T>&&() { return std::move(m_token); }
/**
* @brief Convert the internal attestation token to a public AttestationToken object.
*/
operator Models::AttestationToken<T>&() { return m_token; }
};
}}}} // namespace Azure::Security::Attestation::_detail

View File

@ -140,7 +140,7 @@ namespace Azure { namespace Security { namespace Attestation { namespace _detail
{
throw OpenSSLException("i2d_X509");
}
if (EVP_DigestUpdate(hash.get(), buf, thumbprintBuffer.size()) != 1)
if (EVP_DigestUpdate(hash.get(), thumbprintBuffer.data(), thumbprintBuffer.size()) != 1)
{
throw OpenSSLException("EVP_DigestUpdate");
}

View File

@ -5,6 +5,7 @@
#include "azure/identity/client_secret_credential.hpp"
#include <azure/attestation/attestation_client_models.hpp>
#include <azure/core/test/test_base.hpp>
#include <cstdlib>
#include <gtest/gtest.h>
#include <string>
#include <tuple>

View File

@ -243,13 +243,62 @@ namespace Azure { namespace Core { namespace Test {
"Test Log from: [ " + m_testContext.GetTestPlaybackRecordingName() + " ] - " + message);
}
// Util for tests getting env vars
std::string GetEnv(const std::string& name)
/**
* @brief Utility function used by tests to retrieve env vars
*
* @param name Environment variable name to retrieve.
*
* @return The value of the environment variable retrieved.
*
* @note If AZURE_TENANT_ID, AZURE_CLIENT_ID, or AZURE_CLIENT_SECRET are not available in the
* environment, the AZURE_SERVICE_DIRECTORY environment variable is used to set those values
* with the values emitted by the New-TestResources.ps1 script.
*
* @note The Azure CI pipeline upper cases all environment variables defined in the pipeline.
* Since some operating systems have case sensitive environment variables, on debug builds, this
* function ensures that the environment variable being retrieved is all upper case.
*
*/
std::string GetEnv(std::string const& name)
{
const auto ret = Azure::Core::_internal::Environment::GetVariable(name.c_str());
#if !defined(NDEBUG)
// The azure CI pipeline uppercases all EnvVar values from ci.yml files.
// That means that any mixed case strings will not be found when run from the CI
// pipeline. Check to make sure that the developer only passed in an upper case environment
// variable.
{
if (name != Azure::Core::_internal::StringExtensions::ToUpper(name))
{
throw std::runtime_error("All Azure SDK environment variables must be all upper case.");
}
}
#endif
auto ret = Azure::Core::_internal::Environment::GetVariable(name.c_str());
if (ret.empty())
{
static const char azurePrefix[] = "AZURE_";
if (!m_testContext.IsPlaybackMode() && name.find(azurePrefix) == 0)
{
std::string serviceDirectory
= Azure::Core::_internal::Environment::GetVariable("AZURE_SERVICE_DIRECTORY");
if (serviceDirectory.empty())
{
throw std::runtime_error(
"Could not find a value for " + name
+ " and AZURE_SERVICE_DIRECTORY was not defined. Define either " + name
+ " or AZURE_SERVICE_DIRECTORY to resolve.");
}
// Upper case the serviceName environment variable because all ci.yml environment
// variables are upper cased.
std::string serviceDirectoryEnvVar
= Azure::Core::_internal::StringExtensions::ToUpper(serviceDirectory);
serviceDirectoryEnvVar += name.substr(sizeof(azurePrefix) - 2);
ret = Azure::Core::_internal::Environment::GetVariable(serviceDirectoryEnvVar.c_str());
if (!ret.empty())
{
return ret;
}
}
throw std::runtime_error("Missing required environment variable: " + name);
}
@ -261,6 +310,15 @@ namespace Azure { namespace Core { namespace Test {
/**
* @brief Run before each test.
*
* @param baseRecordingPath - the base recording path to be used for this test. Normally this is
* `AZURE_TEST_RECORDING_DIR`.
*
* For example:
*
* \code{.cpp}
* Azure::Core::Test::TestBase::SetUpTestBase(AZURE_TEST_RECORDING_DIR);
* \endcode
*
*/
void SetUpTestBase(std::string const& baseRecordingPath)
{

View File

@ -33,8 +33,10 @@ namespace Azure { namespace Core { namespace _internal {
static bool LocaleInvariantCaseInsensitiveEqual(
const std::string& lhs,
const std::string& rhs) noexcept;
static std::string const ToLower(const std::string& src) noexcept;
static unsigned char ToLower(const unsigned char src) noexcept;
static std::string const ToLower(std::string const& src) noexcept;
static unsigned char ToLower(unsigned char const src) noexcept;
static std::string const ToUpper(std::string const& src) noexcept;
static unsigned char ToUpper(unsigned char const src) noexcept;
};
}}} // namespace Azure::Core::_internal

View File

@ -303,6 +303,9 @@ void WinHttpTransport::CreateRequestHandle(std::unique_ptr<_detail::HandleManage
{
const std::string& path = handleManager->m_request.GetUrl().GetRelativeUrl();
HttpMethod requestMethod = handleManager->m_request.GetMethod();
bool const requestSecureHttp(
!Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
handleManager->m_request.GetUrl().GetScheme(), HttpScheme));
// Create an HTTP request handle.
handleManager->m_requestHandle = WinHttpOpenRequest(
@ -314,10 +317,7 @@ void WinHttpTransport::CreateRequestHandle(std::unique_ptr<_detail::HandleManage
NULL, // Use HTTP/1.1
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, // No media types are accepted by the client
Azure::Core::_internal::StringExtensions::LocaleInvariantCaseInsensitiveEqual(
handleManager->m_request.GetUrl().GetScheme(), HttpScheme)
? 0
: WINHTTP_FLAG_SECURE); // Uses secure transaction semantics (SSL/TLS)
requestSecureHttp ? WINHTTP_FLAG_SECURE : 0); // Uses secure transaction semantics (SSL/TLS)
if (!handleManager->m_requestHandle)
{
@ -330,6 +330,23 @@ void WinHttpTransport::CreateRequestHandle(std::unique_ptr<_detail::HandleManage
// ERROR_NOT_ENOUGH_MEMORY
GetErrorAndThrow("Error while getting a request handle.");
}
if (requestSecureHttp)
{
// If the service requests TLS client certificates, we want to let the WinHTTP APIs know that
// it's ok to initiate the request without a client certificate.
//
// Note: If/When TLS client certificate support is added to the pipeline, this line may need to
// be revisited.
if (!WinHttpSetOption(
handleManager->m_requestHandle,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
WINHTTP_NO_CLIENT_CERT_CONTEXT,
0))
{
GetErrorAndThrow("Error while setting client cert context to ignore..");
}
}
}
// For PUT/POST requests, send additional data using WinHttpWriteData.

View File

@ -65,11 +65,29 @@ const unsigned char LocaleInvariantLowercaseTable[256] = {
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
};
const unsigned char LocaleInvariantUppercaseTable[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
};
} // unnamed namespace
namespace Azure { namespace Core { namespace _internal {
unsigned char StringExtensions::ToLower(const unsigned char symbol) noexcept
unsigned char StringExtensions::ToLower(unsigned char const symbol) noexcept
{
return LocaleInvariantLowercaseTable[symbol];
}
@ -77,10 +95,23 @@ namespace Azure { namespace Core { namespace _internal {
std::string const StringExtensions::ToLower(const std::string& src) noexcept
{
auto result = std::string(src);
for (auto i = result.begin(); i < result.end(); i++)
{
*i = ToLower(static_cast<unsigned char>(*i));
}
std::transform(result.begin(), result.end(), result.begin(), [](char const ch) {
return StringExtensions::ToLower(ch);
});
return result;
}
unsigned char StringExtensions::ToUpper(unsigned char const symbol) noexcept
{
return LocaleInvariantUppercaseTable[symbol];
}
std::string const StringExtensions::ToUpper(const std::string& src) noexcept
{
auto result = std::string(src);
std::transform(result.begin(), result.end(), result.begin(), [](char const ch) {
return StringExtensions::ToUpper(ch);
});
return result;
}

View File

@ -20,6 +20,24 @@ TEST(String, invariantCompare)
EXPECT_FALSE(StringExtensions::LocaleInvariantCaseInsensitiveEqual("ABC", "abcd"));
}
TEST(String, toLowerC)
{
using Azure::Core::_internal::StringExtensions;
for (unsigned char ch = 0; ch < 255; ch += 1)
{
EXPECT_TRUE(StringExtensions::ToLower(ch) == std::tolower(ch));
}
}
TEST(String, toUpperC)
{
using Azure::Core::_internal::StringExtensions;
for (unsigned char ch = 0; ch < 255; ch += 1)
{
EXPECT_TRUE(StringExtensions::ToUpper(ch) == std::toupper(ch));
}
}
TEST(String, toLower)
{
using Azure::Core::_internal::StringExtensions;
@ -29,9 +47,33 @@ TEST(String, toLower)
EXPECT_TRUE(StringExtensions::ToLower("AA") == "aa");
EXPECT_TRUE(StringExtensions::ToLower("aA") == "aa");
EXPECT_TRUE(StringExtensions::ToLower("ABC") == "abc");
EXPECT_TRUE(
StringExtensions::ToLower("abcdefghijklmnopqrstuvwxyz") == "abcdefghijklmnopqrstuvwxyz");
EXPECT_TRUE(
StringExtensions::ToLower("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == "abcdefghijklmnopqrstuvwxyz");
EXPECT_TRUE(StringExtensions::ToLower("ABC-1-,!@#$%^&*()_+=ABC") == "abc-1-,!@#$%^&*()_+=abc");
EXPECT_FALSE(StringExtensions::ToLower("") == "a");
EXPECT_FALSE(StringExtensions::ToLower("a") == "");
EXPECT_FALSE(StringExtensions::ToLower("a") == "aA");
EXPECT_FALSE(StringExtensions::ToLower("abc") == "abcd");
}
TEST(String, toUpper)
{
using Azure::Core::_internal::StringExtensions;
EXPECT_TRUE(StringExtensions::ToUpper("") == "");
EXPECT_TRUE(StringExtensions::ToUpper("a") == "A");
EXPECT_TRUE(StringExtensions::ToUpper("A") == "A");
EXPECT_TRUE(StringExtensions::ToUpper("AA") == "AA");
EXPECT_TRUE(StringExtensions::ToUpper("aA") == "AA");
EXPECT_TRUE(
StringExtensions::ToUpper("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
EXPECT_TRUE(StringExtensions::ToUpper("ABC") == "ABC");
EXPECT_TRUE(
StringExtensions::ToUpper("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
EXPECT_TRUE(StringExtensions::ToUpper("ABC-1-,!@#$%^&*()_+=ABC") == "ABC-1-,!@#$%^&*()_+=ABC");
EXPECT_FALSE(StringExtensions::ToUpper("") == "A");
EXPECT_FALSE(StringExtensions::ToUpper("a") == "");
EXPECT_FALSE(StringExtensions::ToUpper("a") == "aA");
EXPECT_FALSE(StringExtensions::ToUpper("abc") == "abcd");
}

View File

@ -701,7 +701,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(DownloadBlockBlob, downloadToBuffer)
{
auto const p = GetParam();
BlobConcurrentDownloadParameter const& p(GetParam());
auto const testName(GetTestName(true));
auto client = GetBlockBlobClient(testName);
UploadBlockBlob(8_MB);
@ -773,7 +773,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(DownloadBlockBlob, downloadToFile)
{
auto const p = GetParam();
BlobConcurrentDownloadParameter const& p(GetParam());
auto const testName(GetTestName(true));
auto client = GetBlockBlobClient(testName);
UploadBlockBlob(8_MB);
@ -1191,7 +1191,7 @@ namespace Azure { namespace Storage { namespace Test {
auto const testName(GetTestName());
auto blockBlobClient = GetBlockBlobClient(testName);
SetOptions();
auto const p = GetParam();
UploadBlockBlob::ParamType const& p(GetParam());
auto const blobSize = p.Size;
std::vector<uint8_t> blobContent(static_cast<size_t>(8_MB), 'x');
@ -1227,7 +1227,7 @@ namespace Azure { namespace Storage { namespace Test {
auto const testName(GetTestName());
auto blockBlobClient = GetBlockBlobClient(testName);
SetOptions();
auto const p = GetParam();
UploadBlockBlob::ParamType const& p(GetParam());
auto const blobSize = p.Size;
std::vector<uint8_t> blobContent(static_cast<size_t>(8_MB), 'x');

View File

@ -429,7 +429,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(UploadFile, fromBuffer)
{
auto const p = GetParam();
UploadFile::ParamType const& p(GetParam());
std::vector<uint8_t> fileContent(static_cast<size_t>(8_MB), 'x');
auto fileClient = m_fileSystemClient->GetFileClient(GetTestNameLowerCase());
@ -461,7 +461,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(UploadFile, fromFile)
{
auto const p = GetParam();
UploadFile::ParamType const& p(GetParam());
std::vector<uint8_t> fileContent(static_cast<size_t>(8_MB), 'x');
auto fileClient = m_fileSystemClient->GetFileClient(GetTestNameLowerCase());

View File

@ -395,7 +395,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(UploadShare, fromBuffer)
{
auto const p = GetParam();
UploadShare::ParamType const& p(GetParam());
auto fileClient = m_fileShareDirectoryClient->GetFileClient(m_testName);
std::vector<uint8_t> fileContent(static_cast<size_t>(p.FileSize), 'x');
@ -418,7 +418,7 @@ namespace Azure { namespace Storage { namespace Test {
TEST_P(UploadShare, fromFile)
{
auto const p = GetParam();
UploadShare::ParamType const& p(GetParam());
auto fileClient = m_fileShareDirectoryClient->GetFileClient(m_testName);
std::vector<uint8_t> fileContent = std::vector<uint8_t>(static_cast<size_t>(p.FileSize), 'x');