diff --git a/docs/modules/azure.md b/docs/modules/azure.md index 5e80270e90c..19c141c7639 100644 --- a/docs/modules/azure.md +++ b/docs/modules/azure.md @@ -5,14 +5,73 @@ This module is INCUBATING. While it is ready for use and operational in the curr Testcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java). -Currently, the module supports `CosmosDB` emulator. In order to use it, you should use the following class: +Currently, the module supports `Azurite` and `CosmosDB` emulators. In order to use them, you should use the following classes: Class | Container Image -|- +AzuriteContainer | [mcr.microsoft.com/azure-storage/azurite](https://github.com/microsoft/containerregistry) CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry) ## Usage example +### Azurite Storage Emulator + +Start Azurite Emulator during a test: + + +[Starting a Azurite container](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:emulatorContainer + + +!!! note + SSL configuration is possible using the `withSsl(MountableFile, String)` and `withSsl(MountableFile, MountableFile)` methods. + +If the tested application needs to use more than one set of credentials, the container can be configured to use custom credentials. +Please see some examples below. + + +[Starting a Azurite Blob container with one account and two keys](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:withTwoAccountKeys + + + +[Starting a Azurite Blob container with more accounts and keys](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:withMoreAccounts + + +#### Using with Blob + +Build Azure Blob client: + + +[Build Azure Blob Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createBlobClient + + +In case the application needs to use custom credentials, we can obtain them with a different method: + + +[Obtain connection string with non-default credentials](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:useNonDefaultCredentials + + +#### Using with Queue + +Build Azure Queue client: + + +[Build Azure Queue Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createQueueClient + + +!!! note + We can use custom credentials the same way as defined in the Blob section. + +#### Using with Table + +Build Azure Table client: + + +[Build Azure Table Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createTableClient + + +!!! note + We can use custom credentials the same way as defined in the Blob section. + ### CosmosDB Start Azure CosmosDB Emulator during a test: diff --git a/modules/azure/build.gradle b/modules/azure/build.gradle index d31a549abe5..c6cfb6738d0 100644 --- a/modules/azure/build.gradle +++ b/modules/azure/build.gradle @@ -7,4 +7,7 @@ dependencies { testImplementation 'org.assertj:assertj-core:3.26.3' testImplementation 'com.azure:azure-cosmos:4.63.3' + testImplementation 'com.azure:azure-storage-blob:12.29.0' + testImplementation 'com.azure:azure-storage-queue:12.24.0' + testImplementation 'com.azure:azure-data-tables:12.5.0' } diff --git a/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java new file mode 100644 index 00000000000..56c58df1f1d --- /dev/null +++ b/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java @@ -0,0 +1,167 @@ +package org.testcontainers.azure; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +/** + * Testcontainers implementation for Azurite Emulator. + *

+ * Supported image: {@code mcr.microsoft.com/azure-storage/azurite} + *

+ * Exposed ports: + *

+ */ +public class AzuriteContainer extends GenericContainer { + + private static final String ALLOW_ALL_CONNECTIONS = "0.0.0.0"; + + private static final int DEFAULT_BLOB_PORT = 10000; + + private static final int DEFAULT_QUEUE_PORT = 10001; + + private static final int DEFAULT_TABLE_PORT = 10002; + + private static final String CONNECTION_STRING_FORMAT = + "DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s;BlobEndpoint=%s://%s:%d/%s;QueueEndpoint=%s://%s:%d/%s;TableEndpoint=%s://%s:%d/%s;"; + + /** + * The account name of the default credentials. + */ + private static final String WELL_KNOWN_ACCOUNT_NAME = "devstoreaccount1"; + + /** + * The account key of the default credentials. + */ + private static final String WELL_KNOWN_ACCOUNT_KEY = + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse( + "mcr.microsoft.com/azure-storage/azurite" + ); + + private MountableFile cert = null; + + private String certExtension = null; + + private MountableFile key = null; + + private String pwd = null; + + /** + * @param dockerImageName specified docker image name to run + */ + public AzuriteContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + /** + * @param dockerImageName specified docker image name to run + */ + public AzuriteContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(DEFAULT_BLOB_PORT, DEFAULT_QUEUE_PORT, DEFAULT_TABLE_PORT); + } + + /** + * Configure SSL with a custom certificate and password. + * + * @param pfxCert The PFX certificate file + * @param password The password securing the certificate + * @return this + */ + public AzuriteContainer withSsl(final MountableFile pfxCert, final String password) { + this.cert = pfxCert; + this.pwd = password; + this.certExtension = ".pfx"; + return this; + } + + /** + * Configure SSL with a custom certificate and private key. + * + * @param pemCert The PEM certificate file + * @param pemKey The PEM key file + * @return this + */ + public AzuriteContainer withSsl(final MountableFile pemCert, final MountableFile pemKey) { + this.cert = pemCert; + this.key = pemKey; + this.certExtension = ".pem"; + return this; + } + + @Override + protected void configure() { + withCommand(getCommandLine()); + if (this.cert != null) { + logger().info("Using path for cert file: '{}'", this.cert); + withCopyFileToContainer(this.cert, "/cert" + this.certExtension); + if (this.key != null) { + logger().info("Using path for key file: '{}'", this.key); + withCopyFileToContainer(this.key, "/key.pem"); + } + } + } + + /** + * Returns the connection string for the default credentials. + * + * @return connection string + */ + public String getConnectionString() { + return getConnectionString(WELL_KNOWN_ACCOUNT_NAME, WELL_KNOWN_ACCOUNT_KEY); + } + + /** + * Returns the connection string for the account name and key specified. + * + * @param accountName The name of the account + * @param accountKey The account key + * @return connection string + */ + public String getConnectionString(final String accountName, final String accountKey) { + final String protocol = cert != null ? "https" : "http"; + return String.format( + CONNECTION_STRING_FORMAT, + protocol, + accountName, + accountKey, + protocol, + getHost(), + getMappedPort(DEFAULT_BLOB_PORT), + accountName, + protocol, + getHost(), + getMappedPort(DEFAULT_QUEUE_PORT), + accountName, + protocol, + getHost(), + getMappedPort(DEFAULT_TABLE_PORT), + accountName + ); + } + + String getCommandLine() { + final StringBuilder args = new StringBuilder("azurite"); + args.append(" --blobHost ").append(ALLOW_ALL_CONNECTIONS); + args.append(" --queueHost ").append(ALLOW_ALL_CONNECTIONS); + args.append(" --tableHost ").append(ALLOW_ALL_CONNECTIONS); + if (this.cert != null) { + args.append(" --cert ").append("/cert").append(this.certExtension); + if (this.pwd != null) { + args.append(" --pwd ").append(this.pwd); + } else { + args.append(" --key ").append("/key.pem"); + } + } + final String cmd = args.toString(); + logger().debug("Using command line: '{}'", cmd); + return cmd; + } +} diff --git a/modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java b/modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java new file mode 100644 index 00000000000..2bf0ad856d3 --- /dev/null +++ b/modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java @@ -0,0 +1,266 @@ +package org.testcontainers.azure; + +import com.azure.core.util.BinaryData; +import com.azure.data.tables.TableClient; +import com.azure.data.tables.TableServiceClient; +import com.azure.data.tables.TableServiceClientBuilder; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.queue.QueueClient; +import com.azure.storage.queue.QueueServiceClient; +import com.azure.storage.queue.QueueServiceClientBuilder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.testcontainers.utility.MountableFile; + +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AzuriteContainerTest { + + private static final String PASSWORD = "changeit"; + + private static Properties originalSystemProperties; + + @BeforeClass + public static void captureOriginalSystemProperties() { + originalSystemProperties = (Properties) System.getProperties().clone(); + System.setProperty( + "javax.net.ssl.trustStore", + MountableFile.forClasspathResource("/keystore.pfx").getFilesystemPath() + ); + System.setProperty("javax.net.ssl.trustStorePassword", PASSWORD); + System.setProperty("javax.net.ssl.trustStoreType", "PKCS12"); + } + + @AfterClass + public static void restoreOriginalSystemProperties() { + System.setProperties(originalSystemProperties); + } + + @Test + public void testWithBlobServiceClient() { + try ( + // emulatorContainer { + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + // } + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("BlobEndpoint=http://"); + testBlob(emulator); + } + } + + @Test + public void testWithQueueServiceClient() { + try (AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0")) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("QueueEndpoint=http://"); + testQueue(emulator); + } + } + + @Test + public void testWithTableServiceClient() { + try (AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0")) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("TableEndpoint=http://"); + testTable(emulator); + } + } + + @Test + public void testWithBlobServiceClientWithSslUsingPfx() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl(MountableFile.forClasspathResource("/keystore.pfx"), PASSWORD) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("BlobEndpoint=https://"); + testBlob(emulator); + } + } + + @Test + public void testWithQueueServiceClientWithSslUsingPfx() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl(MountableFile.forClasspathResource("/keystore.pfx"), PASSWORD) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("QueueEndpoint=https://"); + testQueue(emulator); + } + } + + @Test + public void testWithTableServiceClientWithSslUsingPfx() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl(MountableFile.forClasspathResource("/keystore.pfx"), PASSWORD) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("TableEndpoint=https://"); + testTable(emulator); + } + } + + @Test + public void testWithBlobServiceClientWithSslUsingPem() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl( + MountableFile.forClasspathResource("/certificate.pem"), + MountableFile.forClasspathResource("/key.pem") + ) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("BlobEndpoint=https://"); + testBlob(emulator); + } + } + + @Test + public void testWithQueueServiceClientWithSslUsingPem() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl( + MountableFile.forClasspathResource("/certificate.pem"), + MountableFile.forClasspathResource("/key.pem") + ) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("QueueEndpoint=https://"); + testQueue(emulator); + } + } + + @Test + public void testWithTableServiceClientWithSslUsingPem() { + try ( + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withSsl( + MountableFile.forClasspathResource("/certificate.pem"), + MountableFile.forClasspathResource("/key.pem") + ) + ) { + emulator.start(); + assertThat(emulator.getConnectionString()).contains("TableEndpoint=https://"); + testTable(emulator); + } + } + + @Test + public void testTwoAccountKeysWithBlobServiceClient() { + try ( + // withTwoAccountKeys { + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withEnv("AZURITE_ACCOUNTS", "account1:key1:key2") + // } + ) { + emulator.start(); + + String connectionString1 = emulator.getConnectionString("account1", "key1"); + // the second account will have access to the same container using a different key + String connectionString2 = emulator.getConnectionString("account1", "key2"); + + BlobServiceClient blobServiceClient1 = new BlobServiceClientBuilder() + .connectionString(connectionString1) + .buildClient(); + + BlobContainerClient containerClient1 = blobServiceClient1.createBlobContainer("test-container"); + BlobClient blobClient1 = containerClient1.getBlobClient("test-blob.txt"); + blobClient1.upload(BinaryData.fromString("content")); + boolean existsWithAccount1 = blobClient1.exists(); + String contentWithAccount1 = blobClient1.downloadContent().toString(); + + BlobServiceClient blobServiceClient2 = new BlobServiceClientBuilder() + .connectionString(connectionString2) + .buildClient(); + BlobContainerClient containerClient2 = blobServiceClient2.getBlobContainerClient("test-container"); + BlobClient blobClient2 = containerClient2.getBlobClient("test-blob.txt"); + boolean existsWithAccount2 = blobClient2.exists(); + String contentWithAccount2 = blobClient2.downloadContent().toString(); + + assertThat(existsWithAccount1).isTrue(); + assertThat(contentWithAccount1).isEqualTo("content"); + assertThat(existsWithAccount2).isTrue(); + assertThat(contentWithAccount2).isEqualTo("content"); + } + } + + @Test + public void testMultipleAccountsWithBlobServiceClient() { + try ( + // withMoreAccounts { + AzuriteContainer emulator = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withEnv("AZURITE_ACCOUNTS", "account1:key1;account2:key2") + // } + ) { + emulator.start(); + + // useNonDefaultCredentials { + String connectionString1 = emulator.getConnectionString("account1", "key1"); + // the second account will not have access to the same container + String connectionString2 = emulator.getConnectionString("account2", "key2"); + // } + BlobServiceClient blobServiceClient1 = new BlobServiceClientBuilder() + .connectionString(connectionString1) + .buildClient(); + + BlobContainerClient containerClient1 = blobServiceClient1.createBlobContainer("test-container"); + BlobClient blobClient1 = containerClient1.getBlobClient("test-blob.txt"); + blobClient1.upload(BinaryData.fromString("content")); + boolean existsWithAccount1 = blobClient1.exists(); + String contentWithAccount1 = blobClient1.downloadContent().toString(); + + BlobServiceClient blobServiceClient2 = new BlobServiceClientBuilder() + .connectionString(connectionString2) + .buildClient(); + BlobContainerClient containerClient2 = blobServiceClient2.createBlobContainer("test-container"); + BlobClient blobClient2 = containerClient2.getBlobClient("test-blob.txt"); + boolean existsWithAccount2 = blobClient2.exists(); + + assertThat(existsWithAccount1).isTrue(); + assertThat(contentWithAccount1).isEqualTo("content"); + assertThat(existsWithAccount2).isFalse(); + } + } + + private void testBlob(AzuriteContainer container) { + // createBlobClient { + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .connectionString(container.getConnectionString()) + .buildClient(); + // } + BlobContainerClient containerClient = blobServiceClient.createBlobContainer("test-container"); + + assertThat(containerClient.exists()).isTrue(); + } + + private void testQueue(AzuriteContainer container) { + // createQueueClient { + QueueServiceClient queueServiceClient = new QueueServiceClientBuilder() + .connectionString(container.getConnectionString()) + .buildClient(); + // } + QueueClient queueClient = queueServiceClient.createQueue("test-queue"); + + assertThat(queueClient.getQueueUrl()).isNotNull(); + } + + private void testTable(AzuriteContainer container) { + // createTableClient { + TableServiceClient tableServiceClient = new TableServiceClientBuilder() + .connectionString(container.getConnectionString()) + .buildClient(); + // } + TableClient tableClient = tableServiceClient.createTable("testtable"); + + assertThat(tableClient.getTableEndpoint()).isNotNull(); + } +} diff --git a/modules/azure/src/test/resources/certificate.pem b/modules/azure/src/test/resources/certificate.pem new file mode 100644 index 00000000000..30bedc29f45 --- /dev/null +++ b/modules/azure/src/test/resources/certificate.pem @@ -0,0 +1,23 @@ +Bag Attributes + friendlyName: localhost + localKeyID: 54 69 6D 65 20 31 37 33 34 37 32 32 33 32 31 33 31 39 +subject=CN = localhost +issuer=CN = localhost +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIILe7i2bhRE5cwDQYJKoZIhvcNAQEMBQAwFDESMBAGA1UE +AxMJbG9jYWxob3N0MB4XDTI0MTIyMDE5MTg0MVoXDTQ0MTIxNTE5MTg0MVowFDES +MBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAoqYNmLl8IiIoYrXdcoWiMQaM0lOHcV9v3A/THMremHxsR+JPm3FIOAuilFcy +my16kuXIWHfisPxUWr9Vbf8wP/WwZutoOofJrqmruZoorQcNLCs8mQweguRmL1ju +/lDh/9bP626vP9OjwStC4UO4f8Jga8ENoH1U+j1RsIAswYnkk3YIN6YrYv66UvtH +IfR0ERgid2LMBIM+2KD2zw4QRyqXH7Qvo7sCsxdYYHGa6GXfza4vgvce9kJwGqn5 +wiF0Uw9XQbr/LarnR2GCy020OB81KweQJNIh27FZSRLtT+XpsjDRcC2aLBd8CRHd +hwO2zAPI04dLbLM5XAHlEdfT7wIDAQABoz0wOzAdBgNVHQ4EFgQUPqY5isb6Q11Q +t6dbXYHEupxADdMwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 +DQEBDAUAA4IBAQA2katMXrTJBukiNh9yceLO/MewsxvU3KOO/O89ngfjhKXm9T8E +RtENCmp7hLbj1Aj4PRZx3AbmUt9+tRu8fmrRXJQWgUDSHJWjDwSTBOaHcC5LDWSU +Ex4co5Mnxvrimg7tqQg82Hw/yLH9j6gyTyh6v45QETP7IUkTZe4fg75/kPjng7Xg +wp/QXFUx/f0dbvGRl2Fdgg0SnYFqHS3MFIjjFjv8SQlV7rZe+CD1Lxqy/Z6Fd/Fa +33TzTuJeSAG43vdkGAvsNK/KdnxAW03T4l3pVHpNPcvsIvJUMeKOwYOjwHF/eowk +tGrKbpUYFxUr9iKHTfu14t1oExhAsnda2Fcs +-----END CERTIFICATE----- diff --git a/modules/azure/src/test/resources/key.pem b/modules/azure/src/test/resources/key.pem new file mode 100644 index 00000000000..7c635f5a278 --- /dev/null +++ b/modules/azure/src/test/resources/key.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: localhost + localKeyID: 54 69 6D 65 20 31 37 33 34 37 32 32 33 32 31 33 31 39 +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCipg2YuXwiIihi +td1yhaIxBozSU4dxX2/cD9Mcyt6YfGxH4k+bcUg4C6KUVzKbLXqS5chYd+Kw/FRa +v1Vt/zA/9bBm62g6h8muqau5miitBw0sKzyZDB6C5GYvWO7+UOH/1s/rbq8/06PB +K0LhQ7h/wmBrwQ2gfVT6PVGwgCzBieSTdgg3piti/rpS+0ch9HQRGCJ3YswEgz7Y +oPbPDhBHKpcftC+juwKzF1hgcZroZd/Nri+C9x72QnAaqfnCIXRTD1dBuv8tqudH +YYLLTbQ4HzUrB5Ak0iHbsVlJEu1P5emyMNFwLZosF3wJEd2HA7bMA8jTh0tsszlc +AeUR19PvAgMBAAECggEAFT8dzZKFTawqnGJncBtWyZKyeJMiwUOXSCblDADQPRkb +x/QfNA4DQhb7AOe3G6BAP8o2dqAKg9YiasxNq5XHRsOgbIFZ1zN/vAo7/X3OzHN8 +XAW138Q+hBiz5IF4js4gB5yXAokt6WeLH6O4E9cV1dKdZ9YLIqjcnee+sRC9R/a3 +CexqLfC6b77JFbtePfq+5cn2RiK540tO/4k+F+kfJtTg78Wf2RB3A0pBAunhPSd5 +eyjiSvOZtTcvl4GdYw9nKf24I1/WUvt9FH/r1XG0CM5iwuGodbBz1iSUDaQGi5Lf +hFWofXt7eebgsPEKciG4xTyk51p9fy9y8asY+jCbkQKBgQDG2UqJToR8G4Fk6uaO +/XJa15TibIwQDEota0OdlXg2ZR4864fkIBv+UTbymZEM/EBuSdMM1CUBDvYHcFQX +Aj8p1LUyKP2QYwxV/OoPfJ5fBqxqONNR1fLFg7xCxnf9kSvsni2WFneQUrTDl8+7 +qnHm4IKPkAxZ4Orxl5qIBmlpGQKBgQDRZUL3cHIVLLg/aZACpo6SYDYg2bztXmz4 +lRk9j17q1uS83Umzd2lPFmSt/Nr85EKraxXZ/lYPKrP/r1pf1/35eXOWqmYBWgo/ +Hh7OzL12bhvv9UWEY/TvW+wNJNtXlJSjEFRN4tjoG2amYumyhwMO1lIulplUWvtw +ymm8hDjeRwKBgCq7n60KVqZlMtWBNbMc/GpRUgmm0iLQwVApcQp4iLEH4gutgjKg +Q+PPiENyhR2JSD9rVhO3s4warvzCQw/+x5wxvg7diEBzSL9h7tsNKOu6/2qEc8Vu +eRHBUb/37ulrPUlIZPuQMHmvjHFMOrRV2MyJCwXXKxBVqafpsKfy2MxhAoGBAIHH +Cswk6u/ouYDDwjeCVxatfp65lHhhb5RZhD09IIzYBwhu9gC+34veyyNydZ8LMa7g +PbjQAzJ/OvQbEB4a1hPKjDMzBOmNjpAz8NAm4L4H3FTKZP16nhHDnPdAgpkzQzQV +KMrk755bbTFuWH0HZIPLnT+2ou0/PltXeFUYdc59AoGBAIGfWgSOiw7aXbSQZFrO +4S0v3VTwTaiGDVS4pkNRLlhEJUhy8+gbLv/zYDmFmGtqVhXTb/nd6DOdylp+W/HS +8xNWBMWdlX/hVdSK7M0TdJvAaCaMidlquf5qZ2tGNNDeTUN1qbRH26pm8vdNZ3gr +Y/WWJGo0iEmwyB8RcFhvNmuJ +-----END PRIVATE KEY----- diff --git a/modules/azure/src/test/resources/keystore.pfx b/modules/azure/src/test/resources/keystore.pfx new file mode 100644 index 00000000000..3fd2975d3e8 Binary files /dev/null and b/modules/azure/src/test/resources/keystore.pfx differ