Azure Key Vault secrets and C++

Microsoft develops client libraries for the Azure REST API in many different languages, but for C++ only a Storage implementation exists on github called azure-storage-cpp. This means you have to roll your own implementation for the rest. Though it’s probably not a large group that develop in C++ for Azure, there might be circumstances where that is applicable, like in IoT devices or apps written in C++ for portability. If you have many of these apps and they also are located remotely and in public spaces, then you have a potential security issue storing connection strings, etc, in the configuration for the apps.

Azure Key Vault

Azure Key Vault helps safeguard cryptographic keys and secrets by storing them in a “vault” in the cloud. An app can authenticate and retrieve certificates or secrets from the vault which means the app environment is freed from storing stuff you rather don’t want there. Retrieving from Key Vault is an operation that is logged which means you can do auditing of its usage.

With Key Vault you can implement your solution so that it gets its configuration data at runtime. As mentioned before this might lower the security risk by not storing it locally with the app, but it also gives you the benefit of making it possible to change the config data at once place instead of having to change it in all locations the app is installed. Key Vault has more features you can use, like expiring items so that the app will see that it should stop using a secret.

C++, here we go

If this app was written in C#, Java, PHP, Node.js, Python or Ruby, using Key Vault would have been as easy as getting the client libraries and adding a few lines of code. With C++, you have to write your own code on top of the Azure REST APIs for Key Vault.

My sample app retrievs a Storage Account connect string and uses it to upload a file to Blob Storage. The file upload uses the azure-storage-cpp library. Azure-storage-cpp is dependant on a library by Microsoft called Casablanca, which is a portable C++ REST SDK that helps you build client-server communication in native code using asynchronous C++. Casablanca can be used on Windows, Linux, Android or iOIS and since Azure-storage-cpp builds on Casablanca it also works on the same set of platforms.

Building Casablanca and azure-storage-cpp

If you want to build and test my sample, you need to create a Ubuntu Linux 14.04 VM, download and build Casablanca and azure-storage-cpp. How to do that is described in their respective github repos. After you have built these libraries you should have two files named libcpprest.so and libazurestorage.so.

The sample program in action

Running the little program gives the below output. First, it authenticates against Key Vault and then queries for a secret named “stgacctkey” which contains a Storage Account connection string. Using the connection string, it uploads a file (1GB in the below test run) to that storage account.

azkvault-execThe name of the storage container is in a local config file in my example but could have been another secret stored in Key Vault.

In reduced form, these are the steps we do with Key Vault. The KeyVaultClient class is a really simple implementation that in my case just have two methods – Authenticate and GetSecretValue

KeyVaultClient kvc;

kvc.Authenticate( clientId, clientSecret, keyVaultName ).wait();

web::json::value jsonSecret;
bool rc = kvc.GetSecretValue( secretName, jsonSecret );

utility::string_t storage_connection_string = jsonSecret["value"].as_string();
azure::storage::cloud_storage_account storage_account = azure::storage::cloud_storage_account::parse( storage_connection_string );

Key Vault Authentication

Authenticating with Key Vault is a two step process where you first issue a HTTP GET to your key vault resource unauthenticated. Key Vault will respond with a 401 Unauthorized but will include the url to where you can go and get authorization in the WWW-Authenticate header attribute. It also tells you what Azure datacenter the key vault is located in if you have any use of that info

utility::string_t url = "https://" + impl->keyVaultName + ".vault.azure.net/secrets/secretname?api-version=2015-06-01";
http_client client( url ); 
return client.request(methods::GET).then([impl](http_response response)
{
    impl->status_code = response.status_code();
    if ( impl->status_code == 401 ) {
        web::http::http_headers& headers = response.headers();
        impl->keyVaultRegion = headers["x-ms-keyvault-region"];
        const utility::string_t& wwwAuth = headers["WWW-Authenticate"];
        // parse WWW-Authenticate header into url links. Format:
        // Bearer authenticate="url", resource="url"

The second step is making a HTTP POST to the url returned above passing our app identifiers. The response is a JWT Token containing the value we need in subsequent Key Vault calls. The JWT Token is in JSON format and the attribute token_type has the value of “Bearer” and the attribute “access_token” a value of base64 jibberish.

// create the oauth2 authentication request and pass the clientId/Secret as app identifiers
utility::string_t url = impl->loginUrl + "/oauth2/token";
http_client client( url ); 
utility::string_t postData = "resource=" + uri::encode_uri( impl->resourceUrl ) + "&client_id=" + clientId
                            + "&client_secret=" + clientSecret + "&grant_type=client_credentials";
http_request request(methods::POST);
request.headers().add("Content-Type", "application/x-www-form-urlencoded");
request.headers().add("Accept", "application/json");   
request.headers().add("return-client-request-id", "true");   
request.headers().add("client-request-id", NewGuid() );
request.set_body( postData );
// response from IDP is a JWT Token that contains the token type and access token we need for
// Azure HTTP REST API calls
return client.request(request).then([impl](http_response response)
{
    impl->status_code = response.status_code();
    if ( impl->status_code == 200 ) {
        auto bodyStream = response.body();
        concurrency::streams::stringstreambuf sbuffer;
        auto& target = sbuffer.collection();
        bodyStream.read_to_end(sbuffer).get();
        std::error_code err;
        web::json::value jwtToken = web::json::value::parse( target.c_str(), err );
        if ( err.value() == 0 ) {
            impl->tokenType = jwtToken["token_type"].as_string();
            impl->accessToken = jwtToken["access_token"].as_string();
        }
    }
});

Key Vault secret retrieval

Retrieving a secret from Key Vault is as easy as doing a GET request for it and passing the access token as the Authirization header value. The response is JSON formated data where the attribute “value” contains the secret in clear text (or however you stored it in Key Vault)

// create the url path to query the keyvault secret
utility::string_t url = "https://" + impl->keyVaultName + ".vault.azure.net/secrets/" + secretName + "?api-version=2015-06-01";
http_client client( url ); 
http_request request(methods::GET);
request.headers().add("Accept", "application/json");   
request.headers().add("client-request-id", NewGuid() );
// add access token we got from authentication step
request.headers().add("Authorization", impl->tokenType + " " + impl->accessToken );
// Azure HTTP REST API call
return client.request(request).then([impl](http_response response)
{
    std::error_code err;
    impl->status_code = response.status_code();
    if ( impl->status_code == 200 ) {
        auto bodyStream = response.body();
        concurrency::streams::stringstreambuf sbuffer;
        auto& target = sbuffer.collection();
        bodyStream.read_to_end(sbuffer).get();
        impl->secret = web::json::value::parse( target.c_str(), err );
    } else {
    utility::string_t empty = "{\"id\":\"\",\"value\":\"\"}";
    impl->secret = web::json::value::parse( empty.c_str(), err );
    }
});

Using the secret and uploading file to Blob Storage

The azure-storage-cpp C++ implementation of a Blob Client is pretty similar to what you are used to in C# or Java. We extract the secret value and pass it as the connection string to the parse method of cloud_storage_account and with that we have completed the mission of not having to store the connect string locally with the app.

// Initialize Storage Account from KeyVault secret, which holds the connect string
utility::string_t storage_connection_string = jsonSecret["value"].as_string();
azure::storage::cloud_storage_account storage_account = azure::storage::cloud_storage_account::parse( storage_connection_string );

// get container ref
std::wcout << "Using Blob Container: " <<  blobContainer.c_str() << std::endl;
azure::storage::cloud_blob_client blob_client = storage_account.create_cloud_blob_client();
azure::storage::cloud_blob_container container = blob_client.get_container_reference( blobContainer );
container.create_if_not_exists();

time_t t = time(NULL);
struct tm * curtime = localtime( &t );
// upload file
std::wcout << asctime(curtime) << ": Uploading file " <<  fileName.c_str() << std::endl;

concurrency::streams::istream input_stream = concurrency::streams::file_stream<uint8_t>::open_istream( fileName ).get();
azure::storage::cloud_block_blob blob1 = container.get_block_blob_reference( blobName );
blob1.upload_from_stream(input_stream);
input_stream.close().wait();

t = time(NULL);
curtime = localtime( &t );
std::wcout << asctime(curtime) << ": Done!" << std::endl;

Summary

This sample was created based on a customer request who actually wondered how you develop in C++ and interact with Azure Cloud Services and at the same time minimize the footprint of sensitive config data deployed with the app. Casablanca, azure-storage-cpp is a good source of information to start such a task and since there is currently no Key Vault C++ client library, I had to write a simple one.

References

Documentation – What is Azure Key Vault
https://azure.microsoft.com/en-us/documentation/articles/key-vault-whatis/

Github – azure-storage-cpp
https://github.com/Azure/azure-storage-cpp

Github – Casablanca C++ REST SDK
https://github.com/microsoft/cpprestsdk
Make sure to look at the wiki page for documentation on how to build it

Sources

Github repo for sample code
https://github.com/cljung/azkvault

Note 1 – build instructions are in the README.md file in the github repo
Note 2 – I have successfully built and tested the code on Linux and Windows. Mac OS remains to be tested