diff --git a/.github/build-with-maven-native.sh b/.github/build-with-maven-native.sh index 128397ba..9dcbba04 100755 --- a/.github/build-with-maven-native.sh +++ b/.github/build-with-maven-native.sh @@ -5,6 +5,7 @@ set -Euo pipefail # - RESOURCE_GROUP_NAME # - STORAGE_ACCOUNT_NAME # - APP_CONFIG_NAME +# - KEY_VAULT_NAME # Create a resource group az group create \ @@ -71,5 +72,26 @@ credential=$(az appconfig credential list \ export QUARKUS_AZURE_APP_CONFIGURATION_ID=$(echo "${credential}" | jq -r '.id') export QUARKUS_AZURE_APP_CONFIGURATION_SECRET=$(echo "${credential}" | jq -r '.value') +# Azure Key Vault Extension +# The same commands used in +# - integration-tests/README.md +# - integration-tests/azure-keyvault/README.md + +az keyvault create \ + --name ${KEY_VAULT_NAME} \ + --resource-group ${RESOURCE_GROUP_NAME} \ + --location eastus + +az ad signed-in-user show --query id -o tsv \ + | az keyvault set-policy \ + --name ${KEY_VAULT_NAME} \ + --object-id @- \ + --secret-permissions all + +export QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT=$(az keyvault show --name ${KEY_VAULT_NAME} \ + --resource-group ${RESOURCE_GROUP_NAME}\ + --query properties.vaultUri\ + --output tsv) + # Build native executable and run the integration tests against the Azure services mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip -Dazure.test=true diff --git a/.github/delete-azure-resources.sh b/.github/delete-azure-resources.sh index 3ece8f76..bba42ab8 100755 --- a/.github/delete-azure-resources.sh +++ b/.github/delete-azure-resources.sh @@ -4,8 +4,11 @@ # - RESOURCE_GROUP_NAME # - STORAGE_ACCOUNT_NAME # - APP_CONFIG_NAME +# - KEY_VAULT_NAME az appconfig delete --name ${APP_CONFIG_NAME} --resource-group ${RESOURCE_GROUP_NAME} --yes az appconfig purge --name ${APP_CONFIG_NAME} --yes az storage account delete --name ${STORAGE_ACCOUNT_NAME} --resource-group ${RESOURCE_GROUP_NAME} --yes +az keyvault delete --name ${KEY_VAULT_NAME} --resource-group ${RESOURCE_GROUP_NAME} --yes +az keyvault purge --name ${KEY_VAULT_NAME} --yes az group delete --name ${RESOURCE_GROUP_NAME} --yes diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 241e5133..5701ca0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,7 @@ jobs: RESOURCE_GROUP_NAME: quarkus${{ github.run_id }}${{ github.run_number }} STORAGE_ACCOUNT_NAME: storage${{ github.run_id }}${{ github.run_number }} APP_CONFIG_NAME: appconfig${{ github.run_id }}${{ github.run_number }} + KEY_VAULT_NAME: kv${{ github.run_id }}${{ github.run_number }} name: Build on ${{ matrix.os }} strategy: fail-fast: false @@ -67,10 +68,10 @@ jobs: cache: 'maven' - name: Build with Maven - run: mvn -B clean install -Dno-format + run: mvn -B clean install -Dno-format -Dskip.azure.tests=true - name: Build with Maven (Native) - run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip + run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip -Dskip.azure.tests=true if: ${{ env.AZURE_CLIENT_ID == '' || env.AZURE_TENANT_ID == '' || env.AZURE_SUBSCRIPTION_ID == '' }} - name: Az CLI login @@ -85,6 +86,15 @@ jobs: run: .github/build-with-maven-native.sh if: ${{ env.AZURE_CLIENT_ID != '' && env.AZURE_TENANT_ID != '' && env.AZURE_SUBSCRIPTION_ID != '' }} + # Fix error: Client assertion is not within its valid time range. + - name: Az CLI login + uses: azure/login@v1 + with: + client-id: ${{ env.AZURE_CLIENT_ID }} + tenant-id: ${{ env.AZURE_TENANT_ID }} + subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} + if: ${{ env.AZURE_CLIENT_ID != '' && env.AZURE_TENANT_ID != '' && env.AZURE_SUBSCRIPTION_ID != '' }} + - name: Delete Azure resources run: .github/delete-azure-resources.sh if: ${{ always() && env.AZURE_CLIENT_ID != '' && env.AZURE_TENANT_ID != '' && env.AZURE_SUBSCRIPTION_ID != '' }} diff --git a/.github/workflows/quarkus-snapshot.yaml b/.github/workflows/quarkus-snapshot.yaml index 2bd9cd7f..f62f115c 100644 --- a/.github/workflows/quarkus-snapshot.yaml +++ b/.github/workflows/quarkus-snapshot.yaml @@ -42,6 +42,7 @@ jobs: RESOURCE_GROUP_NAME: quarkus${{ github.run_id }}${{ github.run_number }} STORAGE_ACCOUNT_NAME: storage${{ github.run_id }}${{ github.run_number }} APP_CONFIG_NAME: appconfig${{ github.run_id }}${{ github.run_number }} + KEY_VAULT_NAME: kv${{ github.run_id }}${{ github.run_number }} name: "Build against latest Quarkus snapshot" runs-on: ubuntu-latest # Allow to manually launch the ecosystem CI in addition to the bots diff --git a/README.md b/README.md index 6f026f64..0ddf66a1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The following extensions allows you to interact with some of the Azure Services: - [Quarkus Azure App Configuration Extension](https://docs.quarkiverse.io/quarkus-azure-services/dev/quarkus-azure-app-configuration.html): [Azure App Configuration](https://azure.microsoft.com/products/app-configuration) is a fast, scalable parameter storage for app configuration. +- [Quarkus Azure Key Vault Extension](https://docs.quarkiverse.io/quarkus-azure-services/dev/quarkus-azure-keyvault.html): [Azure Key Vault](https://azure.microsoft.com/products/key-vault) is a cloud service for securely storing and accessing secrets. - [Quarkus Azure Blob Storage Extension](https://docs.quarkiverse.io/quarkus-azure-services/dev/quarkus-azure-storage-blob.html): [Azure Blob Storage](https://azure.microsoft.com/products/storage/blobs/) is a massively scalable and secure object storage for cloud-native workloads, archives, data lakes, high-performance computing, and machine learning. @@ -26,6 +27,8 @@ Example applications can be found inside the [integration-tests](integration-tes - [Azure App Configuration sample](integration-tests/azure-app-configuration): REST endpoint using the Quarkus extension to get the configuration stored in Azure App Configuration. +- [Azure Key Vault sample](integration-tests/azure-keyvault): REST endpoint using the Quarkus extension + to create secret via [SecretClient](https://learn.microsoft.com/java/api/com.azure.security.keyvault.secrets.secretclient) and [SecretAsyncClient](https://learn.microsoft.com/java/api/com.azure.security.keyvault.secrets.secretasyncclient) in Azure Key Vault. - [Azure Blob Storage sample](integration-tests/azure-storage-blob): REST endpoint using the Quarkus extension to upload and download files to/from Azure Blob Storage. diff --git a/bom/pom.xml b/bom/pom.xml index 91dbd7c9..e21e95a1 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -25,7 +25,9 @@ 1.2.22 1.0.0-beta.17 - 1.15.0 + 1.14.3 + + 2.5.0 6.6.2 @@ -40,7 +42,12 @@ pom import - + + + net.minidev + json-smart + ${json-smart.verion} + com.microsoft.azure msal4j @@ -67,6 +74,16 @@ + + io.quarkiverse.azureservices + quarkus-azure-identity + ${project.version} + + + io.quarkiverse.azureservices + quarkus-azure-identity-deployment + ${project.version} + io.quarkiverse.azureservices quarkus-azure-core @@ -107,6 +124,16 @@ quarkus-azure-jackson-dataformat-xml-deployment ${project.version} + + io.quarkiverse.azureservices + quarkus-azure-keyvault + ${project.version} + + + io.quarkiverse.azureservices + quarkus-azure-keyvault-deployment + ${project.version} + io.quarkiverse.azureservices quarkus-azure-storage-blob diff --git a/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-deleted-secret-portal.png b/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-deleted-secret-portal.png new file mode 100644 index 00000000..cebfaf2e Binary files /dev/null and b/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-deleted-secret-portal.png differ diff --git a/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-secret-portal.png b/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-secret-portal.png new file mode 100644 index 00000000..12f90b78 Binary files /dev/null and b/docs/modules/ROOT/assets/images/quarkus-azure-keyvault-secret-portal.png differ diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index bd388a4b..f3a47c1b 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1,2 +1,3 @@ * xref:quarkus-azure-app-configuration.adoc[Quarkus Azure App Configuration Extension] +* xref:quarkus-azure-keyvault.adoc[Quarkus Azure Key Vault Extension] * xref:quarkus-azure-storage-blob.adoc[Quarkus Azure Blob Storage Extension] diff --git a/docs/modules/ROOT/pages/includes/quarkus-azure-keyvault.adoc b/docs/modules/ROOT/pages/includes/quarkus-azure-keyvault.adoc new file mode 100644 index 00000000..c7309917 --- /dev/null +++ b/docs/modules/ROOT/pages/includes/quarkus-azure-keyvault.adoc @@ -0,0 +1,29 @@ + +:summaryTableId: quarkus-azure-keyvault +[.configuration-legend] +icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime +[.configuration-reference.searchable, cols="80,.^10,.^10"] +|=== + +h|[[quarkus-azure-keyvault_configuration]]link:#quarkus-azure-keyvault_configuration[Configuration property] + +h|Type +h|Default + +a| [[quarkus-azure-keyvault_quarkus.quarkus.azure.keyvault.secret.endpoint]]`link:#quarkus-azure-keyvualt_quarkus.azure.keyvault.secret.endpoint[quarkus.azure.keyvault.secret.endpoint]` + + +[.description] +-- +The endpoint of Azure Key Vault. + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT+++` +endif::add-copy-button-to-env-var[] +--|string +|required icon:exclamation-circle[title=Configuration property is required] + +|=== \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 21c1150f..dbe2f93f 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -4,4 +4,6 @@ include::./includes/attributes.adoc[] include::quarkus-azure-app-configuration.adoc[leveloffset=+1, opts=optional] +include::quarkus-azure-keyvault.adoc[leveloffset=+1, opts=optional] + include::quarkus-azure-storage-blob.adoc[leveloffset=+1, opts=optional] diff --git a/docs/modules/ROOT/pages/quarkus-azure-keyvault.adoc b/docs/modules/ROOT/pages/quarkus-azure-keyvault.adoc new file mode 100644 index 00000000..cd490e49 --- /dev/null +++ b/docs/modules/ROOT/pages/quarkus-azure-keyvault.adoc @@ -0,0 +1,167 @@ += Quarkus Azure Key Vault Extension + +include::./includes/attributes.adoc[] + +include::./includes/support.adoc[] + +https://azure.microsoft.com/en-us/products/key-vault[Azure Key Vault] is a cloud service for securely storing and accessing secrets. A secret is anything that you want to tightly control access to, such as API keys, passwords, certificates, or cryptographic keys. +This extension allows you to create and retrieve secret from Azure Key Vault by injecting the following object inside your Quarkus application. + +* `com.azure.security.keyvault.secrets.SecretClient` +* `com.azure.security.keyvault.secrets.SecretAsyncClient` + +The extension produces SecretClient and SecretAsyncClient using https://learn.microsoft.com/java/api/overview/azure/identity-readme#defaultazurecredential[DefaultAzureCredential]. +Developers who want more control or whose scenario isn't served by the default settings should build client using other credential types. + +== Installation + +If you want to use this extension, you need to add the `io.quarkiverse.azureservices:quarkus-azure-services` extension first to your build file. + +For instance, with Maven, add the following dependency to your POM file: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.azureservices + quarkus-azure-keyvault + {project-version} + +---- + +== How to Use It + +Once you have added the extension to your project, follow the next steps, so you can inject `com.azure.security.keyvault.secrets.SecretClient` or `com.azure.security.keyvault.secrets.SecretAsyncClient` object in your application to manage secret. + +=== Setup your Azure Environment + +include::includes/azure-setup-environment.adoc[] + +Create an Azure resource group with the `az group create` command. +A resource group is a logical container into which Azure resources are deployed and managed. + +[source,shell] +---- +az group create \ + --name rg-quarkus-azure-keyvault \ + --location eastus +---- + +Create a general-purpose key vault with the following command: + +[source,shell] +---- +az keyvault create --name kvquarkusazurekv0423 \ + --resource-group rg-quarkus-azure-keyvault \ + --location eastus +---- + +Key Vault provides secure storage of generic secrets, such as passwords and database connection strings. +All secrets in your key vault are stored encrypted. The Azure Key Vault service encrypts your secrets when you add them, and decrypts them automatically when you read them. + +Access Control for secrets managed in Key Vault, is provided at the level of the Key Vault. +The following command uses your Azure AD account to authorize the operation to manage secret. +Even if you are the key vault owner, you need explicit permissions to perform operations against secret. + +Assign all secret permissions(backup, delete, get, list, purge, recover, restore, set) to yourself: + +[source,shell] +---- +az ad signed-in-user show --query id -o tsv \ + | az keyvault set-policy \ + --name kvquarkusazurekv0423 \ + --object-id @- \ + --secret-permissions all +---- + +If you log into the http://portal.azure.com/[Azure portal], you can see the key vault you created. Select **Objects** -> **Secrets**, you will find the Secrets page. + +image::quarkus-azure-keyvault-secret-portal.png[alt=Azure Portal showing Key Vault Secrets] + +=== Configure the Azure Key Vault Secret Client + +As you can see below in the _Configuration Reference_ section, the configuration option `quarkus.azure.keyvault.secret.endpoint` is mandatory. +To get the endpoint, execute the following Azure CLI command: + +[source,shell] +---- +az keyvault show --name kvquarkusazurekv0423 \ + --resource-group rg-quarkus-azure-keyvault \ + --query properties.vaultUri \ + --output tsv +---- + +Then, in the `application.properties` file, add the following property: + +[source,properties] +---- +quarkus.azure.keyvault.secret.endpoint=https://kvquarkusazurekv0423.vault.azure.net/ +---- + +=== Inject the Azure Key Vault Secret Client + +Now that your Azure environment is ready and that you have configured the extension, you can inject the `com.azure.security.keyvault.secrets.SecretClient` object in your application, so you can interact with Azure Key Vault Secret. + +This is a https://quarkus.io/guides/command-mode-reference[Quarkus CLI application]. The application will: + +* Ask for a secret value. +* Create a secret with name `mySecret` and set its value. +* Retrieve and print the secret value. +* Delete the secret. + +You can build and run the application in development mode using command: + +``` +quarkus dev +``` + +[source,java] +---- +@QuarkusMain +public class KeyVaultSecretApplication implements QuarkusApplication { + + @Inject + SecretClient secretClient; + + @Override + public int run(String... args) throws Exception { + + Console con = System.console(); + + String secretName = "mySecret"; + System.out.println("Create secret: " + secretName); + + System.out.println("Please provide the value of your secret > "); + + String secretValue = con.readLine(); + + System.out.println("Creating a secret called '" + secretName + "' with value '" + secretValue + "' ... "); + + secretClient.setSecret(new KeyVaultSecret(secretName, secretValue)); + + System.out.println("Retrieving your secret..."); + + KeyVaultSecret retrievedSecret = secretClient.getSecret(secretName); + + System.out.println("Your secret's value is '" + retrievedSecret.getValue() + "'."); + System.out.println("Deleting your secret ... "); + + SyncPoller deletionPoller = secretClient.beginDeleteSecret(secretName); + deletionPoller.waitForCompletion(); + + System.out.println("done."); + return 0; + } +} + +---- + +After running the application, if you log into the http://portal.azure.com/[Azure portal], you can see the key vault and the secret you created. +As the secret is deleted, you will find the secret from **Objects** -> **Secrets** -> **Manage deleted secrets**. + +image::quarkus-azure-keyvault-deleted-secret-portal.png[alt=Azure Portal showing the deleted secrets] + +You can also inject `com.azure.security.keyvault.secrets.SecretAsyncClient` object to your application. For more usage, see https://learn.microsoft.com/java/api/com.azure.security.keyvault.secrets.secretasyncclient?view=azure-java-stable[com.azure.security.keyvault.secrets.secretasyncclient]. + +== Extension Configuration Reference + +include::includes/quarkus-azure-keyvault.adoc[leveloffset=+1, opts=optional] diff --git a/docs/pom.xml b/docs/pom.xml index ae711afd..152a1a47 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -38,6 +38,18 @@ + + io.quarkiverse.azureservices + quarkus-azure-keyvault-deployment + ${project.version} + test + + + * + * + + + io.quarkiverse.azureservices quarkus-azure-http-client-vertx-deployment @@ -100,6 +112,11 @@ quarkus-azure-app-configuration.adoc false + + ${project.basedir}/../target/asciidoc/generated/config/ + quarkus-azure-keyvault.adoc + false + ${project.basedir}/../target/asciidoc/generated/config/ quarkus-azure-storage-blob.adoc diff --git a/extensions/azure-keyvault/README.md b/extensions/azure-keyvault/README.md new file mode 100644 index 00000000..a3fb2676 --- /dev/null +++ b/extensions/azure-keyvault/README.md @@ -0,0 +1,3 @@ +# Quarkus Azure Key Vault Extension + +[Check the documentation](https://quarkiverse.github.io/quarkiverse-docs/quarkus-azure-services/dev/quarkus-azure-keyvault.html). diff --git a/extensions/azure-keyvault/deployment/pom.xml b/extensions/azure-keyvault/deployment/pom.xml new file mode 100644 index 00000000..2d52615d --- /dev/null +++ b/extensions/azure-keyvault/deployment/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-keyvault-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-azure-keyvault-deployment + Quarkus Azure Services :: Extension :: Azure Key Vault :: Deployment + Manage secrets in Azure Key Vault Service + + + + io.quarkiverse.azureservices + quarkus-azure-http-client-vertx-deployment + + + io.quarkiverse.azureservices + quarkus-azure-identity-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkiverse.azureservices + quarkus-azure-keyvault + + + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/extensions/azure-keyvault/deployment/src/main/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretProcessor.java b/extensions/azure-keyvault/deployment/src/main/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretProcessor.java new file mode 100644 index 00000000..0ac502a9 --- /dev/null +++ b/extensions/azure-keyvault/deployment/src/main/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretProcessor.java @@ -0,0 +1,109 @@ +package io.quarkiverse.azure.keyvault.secret.deployment; + +import java.util.Collection; +import java.util.List; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +import com.azure.json.JsonSerializable; + +import io.quarkiverse.azure.keyvault.secret.runtime.KeyVaultSecretClientProducer; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; +import io.quarkus.logging.Log; + +public class KeyVaultSecretProcessor { + + static final String FEATURE = "azure-keyvault-secret"; + private static final DotName JSON_SERIALIZABLE_DOT_NAME = DotName.createSimple(JsonSerializable.class.getName()); + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + AdditionalBeanBuildItem producer() { + return new AdditionalBeanBuildItem(KeyVaultSecretClientProducer.class); + } + + @BuildStep + ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { + return new ExtensionSslNativeSupportBuildItem(FEATURE); + } + + @BuildStep + IndexDependencyBuildItem indexDependency() { + return new IndexDependencyBuildItem("com.azure", "azure-security-keyvault-secrets"); + } + + @BuildStep + void reflectiveClasses(CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer reflectiveClasses) { + + // see https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/keyvault/azure-security-keyvault-secrets/src/main/resources/META-INF/native-image/com.azure/azure-security-keyvault-secrets/reflect-config.json + reflectiveClasses.produce( + ReflectiveClassBuildItem + .builder("com.azure.security.keyvault.secrets.SecretAsyncClient", + "com.azure.security.keyvault.secrets.SecretClient", + "com.azure.security.keyvault.secrets.SecretClientBuilder", + "com.azure.security.keyvault.secrets.SecretServiceVersion", + "com.azure.security.keyvault.secrets.implementation.DeletedSecretPage", + "com.azure.security.keyvault.secrets.implementation.KeyVaultCredentialPolicy", + "com.azure.security.keyvault.secrets.implementation.KeyVaultErrorCodeStrings", + "com.azure.security.keyvault.secrets.implementation.SecretBackup", + "com.azure.security.keyvault.secrets.implementation.SecretClientImpl", + "com.azure.security.keyvault.secrets.implementation.SecretPropertiesPage", + "com.azure.security.keyvault.secrets.implementation.SecretRequestAttributes", + "com.azure.security.keyvault.secrets.implementation.SecretRequestParameters", + "com.azure.security.keyvault.secrets.implementation.SecretRestoreRequestParameters", + "com.azure.security.keyvault.secrets.implementation.models.DeletedSecretBundle", + "com.azure.security.keyvault.secrets.implementation.models.DeletionRecoveryLevel", + "com.azure.security.keyvault.secrets.implementation.models.KeyVaultErrorException", + "com.azure.security.keyvault.secrets.implementation.models.SecretsModelsUtils") + .methods().build()); + } + + @BuildStep + void reflectiveHierarchyClass(CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer reflectiveHierarchyClass) { + + final var fullIndex = combinedIndexBuildItem.getIndex(); + Collection jsonSerializableImpls = fullIndex.getAllKnownImplementors(JSON_SERIALIZABLE_DOT_NAME); + jsonSerializableImpls + .stream() + .map(c -> c.name().toString()) + .filter(s -> s.startsWith("com.azure.security.keyvault.secrets.implementation.models.")) + .forEach(e -> { + if (Log.isDebugEnabled()) { + Log.debugv("Add class to reflectiveHierarchyClass: " + e); + } + Type jandexType = Type.create(DotName.createSimple(e), Type.Kind.CLASS); + reflectiveHierarchyClass.produce(new ReflectiveHierarchyBuildItem.Builder() + .type(jandexType) + .source(getClass().getSimpleName() + " > " + jandexType.name().toString()) + .build()); + }); + + } + + @BuildStep + void proxyDefinitions( + CombinedIndexBuildItem combinedIndex, + BuildProducer proxyDefinitions) { + + proxyDefinitions.produce(new NativeImageProxyDefinitionBuildItem( + List.of("com.azure.security.keyvault.secrets.implementation.SecretClientImpl$SecretClientService"))); + } + +} diff --git a/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretAsyncClientCDITest.java b/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretAsyncClientCDITest.java new file mode 100644 index 00000000..1f48679b --- /dev/null +++ b/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretAsyncClientCDITest.java @@ -0,0 +1,26 @@ +package io.quarkiverse.azure.keyvault.secret.deployment; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.azure.security.keyvault.secrets.SecretAsyncClient; + +import io.quarkus.test.QuarkusUnitTest; + +public class KeyVaultSecretAsyncClientCDITest { + + @Inject + SecretAsyncClient asyncClient; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("application.properties")); + + @Test + public void test() { + // Application should start with az login. + } +} diff --git a/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretClientCDITest.java b/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretClientCDITest.java new file mode 100644 index 00000000..c2d5ab71 --- /dev/null +++ b/extensions/azure-keyvault/deployment/src/test/java/io/quarkiverse/azure/keyvault/secret/deployment/KeyVaultSecretClientCDITest.java @@ -0,0 +1,26 @@ +package io.quarkiverse.azure.keyvault.secret.deployment; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.azure.security.keyvault.secrets.SecretClient; + +import io.quarkus.test.QuarkusUnitTest; + +public class KeyVaultSecretClientCDITest { + + @Inject + SecretClient client; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("application.properties")); + + @Test + public void test() { + // Application should start with az login. + } +} diff --git a/extensions/azure-keyvault/deployment/src/test/resources/application.properties b/extensions/azure-keyvault/deployment/src/test/resources/application.properties new file mode 100644 index 00000000..efeeb94d --- /dev/null +++ b/extensions/azure-keyvault/deployment/src/test/resources/application.properties @@ -0,0 +1 @@ +quarkus.azure.keyvault.secret.endpoint=https://localhost:8082 \ No newline at end of file diff --git a/extensions/azure-keyvault/pom.xml b/extensions/azure-keyvault/pom.xml new file mode 100644 index 00000000..f774f886 --- /dev/null +++ b/extensions/azure-keyvault/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-extensions + 999-SNAPSHOT + ../pom.xml + + + quarkus-azure-keyvault-parent + Quarkus Azure Services :: Extension :: Azure Key Vault + pom + + + deployment + runtime + + diff --git a/extensions/azure-keyvault/runtime/pom.xml b/extensions/azure-keyvault/runtime/pom.xml new file mode 100644 index 00000000..0d7a7907 --- /dev/null +++ b/extensions/azure-keyvault/runtime/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-keyvault-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-azure-keyvault + Quarkus Azure Services :: Extension :: Azure Key Vault :: Runtime + Manage secrets in Azure Key Vault Service + + + + io.quarkiverse.azureservices + quarkus-azure-http-client-vertx + + + io.quarkiverse.azureservices + quarkus-azure-identity + + + com.azure + azure-security-keyvault-secrets + + + com.azure + azure-core-http-netty + + + + + io.quarkus + quarkus-arc + + + io.quarkiverse.azureservices + quarkus-azure-core-util + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + compile + + extension-descriptor + + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + \ No newline at end of file diff --git a/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretClientProducer.java b/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretClientProducer.java new file mode 100644 index 00000000..5b6e07af --- /dev/null +++ b/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretClientProducer.java @@ -0,0 +1,37 @@ +package io.quarkiverse.azure.keyvault.secret.runtime; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import com.azure.core.util.ClientOptions; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.security.keyvault.secrets.SecretAsyncClient; +import com.azure.security.keyvault.secrets.SecretClient; +import com.azure.security.keyvault.secrets.SecretClientBuilder; + +import io.quarkiverse.azure.core.util.AzureQuarkusIdentifier; + +public class KeyVaultSecretClientProducer { + + @Inject + KeyVaultSecretConfig secretConfiguration; + + @Produces + public SecretClient createSecretClient() { + return new SecretClientBuilder() + .clientOptions(new ClientOptions().setApplicationId(AzureQuarkusIdentifier.AZURE_QUARKUS_KEY_VAULT_SYNC_CLIENT)) + .vaultUrl(secretConfiguration.endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + + @Produces + public SecretAsyncClient createSecretAsyncClient() { + return new SecretClientBuilder() + .clientOptions( + new ClientOptions().setApplicationId(AzureQuarkusIdentifier.AZURE_QUARKUS_KEY_VAULT_ASYNC_CLIENT)) + .vaultUrl(secretConfiguration.endpoint) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildAsyncClient(); + } +} diff --git a/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretConfig.java b/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretConfig.java new file mode 100644 index 00000000..560ff780 --- /dev/null +++ b/extensions/azure-keyvault/runtime/src/main/java/io/quarkiverse/azure/keyvault/secret/runtime/KeyVaultSecretConfig.java @@ -0,0 +1,15 @@ +package io.quarkiverse.azure.keyvault.secret.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "azure.keyvault.secret", phase = ConfigPhase.RUN_TIME) +public class KeyVaultSecretConfig { + + /** + * The endpoint of Azure Key Vault Secret. + */ + @ConfigItem + public String endpoint; +} diff --git a/extensions/azure-keyvault/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/azure-keyvault/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..c6493a31 --- /dev/null +++ b/extensions/azure-keyvault/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,18 @@ +--- +name: "Quarkus Support Azure Key Vault" +description: "Manage secrets in Azure Key Vault Service" +artifact: ${project.groupId}:${project.artifactId}:${project.version} +metadata: + icon-url: "https://learn.microsoft.com/en-us/media/logos/logo_azure.svg" + short-name: "azure-keyvault" + keywords: + - "keyvault" + - "keyvault-secret" + - "azure" + - "azure-keyvault-secret" + guide: "https://docs.quarkiverse.io/quarkus-azure-services/dev/quarkus-azure-key-vault.html" + categories: + - "cloud" + status: "preview" + config: + - "quarkus.azure.keyvault." diff --git a/extensions/pom.xml b/extensions/pom.xml index 00894818..88975237 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -15,6 +15,7 @@ azure-app-configuration + azure-keyvault azure-storage-blob diff --git a/integration-tests/README.md b/integration-tests/README.md index 366e69c2..b3cf8399 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -1,6 +1,6 @@ # Quarkus Azure Services - Integration Tests -This is the integration test for testing all Quarkus Azure services extensions from REST endpoints. +This is the integration test for testing all Quarkus Azure services extensions from REST endpoints. You can also find the same AZ CLI commands to create Azure services in `.github/build-with-maven-native.sh`. ## Installing dependencies locally in development iteration @@ -117,6 +117,33 @@ will be fed into config properties `quarkus.azure.app.configuration.endpoint` / `quarkus.azure.app.configuration.id` / `quarkus.azure.app.configuration.secret` of `azure-app-configuration` extension in order to set up the connection to the Azure App Configuration store. +### Creating Azure Key Vault + +Run the following commands to create an Azure Key Vault, set permission and export its connection string as an environment +variable. + +``` +export KEY_VAULT_NAME= +az keyvault create --name ${KEY_VAULT_NAME} \ + --resource-group ${RESOURCE_GROUP_NAME} \ + --location eastus + +az ad signed-in-user show --query id -o tsv \ + | az keyvault set-policy \ + --name ${KEY_VAULT_NAME} \ + --object-id @- \ + --secret-permissions all + +export QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT=$(az keyvault show --name ${KEY_VAULT_NAME}\ + --resource-group ${RESOURCE_GROUP_NAME}\ + --query properties.vaultUri -otsv) +echo "The value of 'quarkus.azure.keyvault.secret.endpoint' is: ${QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT}" +``` + +The value of environment variable `QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT` will be fed into config +property `quarkus.azure.keyvault.secret.endpoint` of `azure-keyvault` extension in order to set up the +connection to the Azure Key Vault. + ### Running the test Finally, build the native executable and launch the test with: diff --git a/integration-tests/azure-keyvault/README.md b/integration-tests/azure-keyvault/README.md new file mode 100644 index 00000000..5a3197ef --- /dev/null +++ b/integration-tests/azure-keyvault/README.md @@ -0,0 +1,163 @@ +# Azure Key Vault sample + +This is a sample application using the Quarkus Key Vault extension to create secret with `SecretClient` and `SecretAsyncClient` from Azure Key Vault. + +## Prerequisites + +To successfully run this sample, you need: + +* JDK 11+ installed with JAVA_HOME configured appropriately +* Apache Maven 3.8.6+ +* Azure CLI and Azure subscription if the specific Azure services are required +* Docker if you want to build the app as a native executable + +You also need to make sure the right version of dependencies are installed. + +### Use development iteration version + +By default, the sample depends on the development iteration version, which is `999-SNAPSHOT`. To install the development +iteration version, you need to build it locally. + +``` +# Switch to the root directory of Quarkus Azure services extensions. +# For example, if you are in the directory of quarkus-azure-services/integration-tests/azure-keyvault +cd ../.. + +# Install all Quarkus Azure services extensions locally. +mvn clean install -DskipTests + +# Switch back to the directory of integration-tests/azure-keyvault +cd integration-tests/azure-keyvault +``` + +### Use release version + +If you want to use the release version, you need to update the version of dependencies in the `pom.xml` file. + +First, you need to find out the latest release version of the Quarkus Azure services extensions +from [releases](https://github.com/quarkiverse/quarkus-azure-services/releases), for example, `1.0.0`. + +Then, update the version of dependencies in the `pom.xml` file, for example: + +```xml + + + io.quarkiverse.azureservices + quarkus-azure-services-parent + 1.0.0 + + +``` + +## Preparing the Azure services + +You need to create an Azure Key Vault before running the sample application. + +### Logging into Azure + +Log into Azure and create a resource group for hosting the Key Vault to be created. + +``` +az login + +RESOURCE_GROUP_NAME= +az group create \ + --name ${RESOURCE_GROUP_NAME} \ + --location eastus +``` + +### Creating Azure Key Vault + +Run the following commands to create an Azure Key Vault, set permission and export its endpoint as an environment +variable. You can also find the same AZ CLI commands to create Azure Key Vault services in `.github/build-with-maven-native.sh`. + +``` +KEY_VAULT_NAME= +az keyvault create --name ${KEY_VAULT_NAME} \ + --resource-group ${RESOURCE_GROUP_NAME} \ + --location eastus + +az ad signed-in-user show --query id -o tsv \ + | az keyvault set-policy \ + --name ${KEY_VAULT_NAME} \ + --object-id @- \ + --secret-permissions all + +export QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT=$(az keyvault show --name ${KEY_VAULT_NAME}\ + --resource-group ${RESOURCE_GROUP_NAME}\ + --query properties.vaultUri -otsv) +echo "The value of 'quarkus.azure.keyvault.secret.endpoint' is: ${QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT}" +``` + +The value of environment variable `QUARKUS_AZURE_KEYVAULT_SECRET_ENDPOINT` will be fed into config +property `quarkus.azure.keyvault.secret.endpoint` of `azure-keyvault` extension in order to set up the +connection to the Azure Key Vault. + +You can also manually copy the output of the variable `quarkus.azure.keyvault.secret.endpoint` and then +update [application.properties](src/main/resources/application.properties) by uncommenting the +same property and setting copied value. + +## Running the sample + +You have different choices to run the sample. + +### Running the sample in development mode + +First, you can launch the sample in `dev` mode. + +``` +mvn quarkus:dev +``` + +### Running and test the sample in JVM mode + +You can also run the sample in JVM mode. Make sure you have +followed [Preparing the Azure services](#preparing-the-azure-services) to create the required Azure services. + +``` +# Build the package. +mvn package + +# Run the generated jar file. +java -jar ./target/quarkus-app/quarkus-run.jar +``` + +### Running and test the sample as a native executable + +You can even run the sample as a native executable. Make sure you have installed Docker and +followed [Preparing the Azure services](#preparing-the-azure-services) to create the required Azure services. + +``` +# Build the native executable using the Docker. +mvn package -Dnative + +# Run the native executable. +version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +./target/quarkus-azure-integration-test-keyvault-secret-${version}-runner +``` + +## ## Testing the sample + +Open a new terminal and run the following commands to test the sample: + +``` +#Use SecretClient to create a secret and get the value: +curl http://localhost:8080/keyvault/sync + +#Use SecretAsyncClient to create a secret and get the value: +curl http://localhost:8080/keyvault/async +``` + +Now you will + +Press `Ctrl + C` to stop the sample once you complete the try and test. + +## Cleaning up Azure resources + +Run the following command to clean up the Azure resources if you created before: + +``` +az group delete \ + --name ${RESOURCE_GROUP_NAME} \ + --yes --no-wait +``` \ No newline at end of file diff --git a/integration-tests/azure-keyvault/pom.xml b/integration-tests/azure-keyvault/pom.xml new file mode 100644 index 00000000..d5eeb6aa --- /dev/null +++ b/integration-tests/azure-keyvault/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-services-parent + 999-SNAPSHOT + ../../pom.xml + + + quarkus-azure-integration-test-keyvault-secret + Quarkus Azure Services :: Integration Test :: Azure Key Vault Secret + + + + + ${skip.azure.tests} + + + + + io.quarkus + quarkus-rest + + + io.quarkiverse.azureservices + quarkus-azure-keyvault + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + native + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + + + + + false + native + + + + diff --git a/integration-tests/azure-keyvault/src/main/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResource.java b/integration-tests/azure-keyvault/src/main/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResource.java new file mode 100644 index 00000000..11e1ccb0 --- /dev/null +++ b/integration-tests/azure-keyvault/src/main/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResource.java @@ -0,0 +1,54 @@ +package io.quarkiverse.azure.keyvault.secret.it; + +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; + +import java.util.concurrent.CompletableFuture; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.jboss.logging.Logger; + +import com.azure.security.keyvault.secrets.SecretAsyncClient; +import com.azure.security.keyvault.secrets.SecretClient; + +@Path("/keyvault") +public class KeyVaultSecretResource { + private static final Logger LOG = Logger.getLogger(KeyVaultSecretResource.class); + + public final static String TEXT = "Quarkus Azure Key Vault Extension is awsome"; + private static final String SYNC_PARAM = "synkv" + System.currentTimeMillis(); + private static final String ASYNC_PARAM = "asynckv" + System.currentTimeMillis(); + + @Inject + SecretClient secretClient; + + @Inject + SecretAsyncClient secretAsyncClient; + + @GET + @Path("sync") + @Produces(TEXT_PLAIN) + public String testSync() { + LOG.info("Testing SecretClient by creating secret: " + SYNC_PARAM); + //Put parameter + secretClient.setSecret(SYNC_PARAM, TEXT); + //Get parameter + return secretClient.getSecret(SYNC_PARAM).getValue(); + } + + @GET + @Path("async") + @Produces(TEXT_PLAIN) + public CompletableFuture testAsync() { + LOG.info("Testing SecretAsyncClient by creating secret: " + ASYNC_PARAM); + + CompletableFuture completableFuture = new CompletableFuture<>(); + secretAsyncClient.setSecret(ASYNC_PARAM, TEXT) + .subscribe(secret -> completableFuture.complete(secret.getValue())); + + return completableFuture.toCompletableFuture(); + } +} diff --git a/integration-tests/azure-keyvault/src/main/resources/application.properties b/integration-tests/azure-keyvault/src/main/resources/application.properties new file mode 100644 index 00000000..ec60c8d4 --- /dev/null +++ b/integration-tests/azure-keyvault/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#Uncomment and set a valid endpoint of an Azure Key Vault like https://.vault.azure.net/ +#quarkus.azure.keyvault.secret.endpoint= diff --git a/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceIT.java b/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceIT.java new file mode 100644 index 00000000..65d8d9ab --- /dev/null +++ b/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkiverse.azure.keyvault.secret.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class KeyVaultSecretResourceIT extends KeyVaultSecretResourceTest { + +} diff --git a/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceTest.java b/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceTest.java new file mode 100644 index 00000000..19e03578 --- /dev/null +++ b/integration-tests/azure-keyvault/src/test/java/io/quarkiverse/azure/keyvault/secret/it/KeyVaultSecretResourceTest.java @@ -0,0 +1,22 @@ +package io.quarkiverse.azure.keyvault.secret.it; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +public class KeyVaultSecretResourceTest { + + @Test + public void testSecretClient() { + RestAssured.when().get("/keyvault/sync").then().body(is("Quarkus Azure Key Vault Extension is awsome")); + } + + @Test + public void testSecretAsyncClient() { + RestAssured.when().get("/keyvault/async").then().body(is("Quarkus Azure Key Vault Extension is awsome")); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 448b61fe..a356e001 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -15,7 +15,8 @@ azure-app-configuration - azure-storage-blob + azure-keyvault + azure-storage-blob diff --git a/internal/azure-identity/deployment/pom.xml b/internal/azure-identity/deployment/pom.xml new file mode 100644 index 00000000..1cab574e --- /dev/null +++ b/internal/azure-identity/deployment/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-identity-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-azure-identity-deployment + Quarkus Azure Services :: Internal :: Azure Identity :: Deployment + + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-netty-deployment + + + io.quarkiverse.azureservices + quarkus-azure-identity + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/internal/azure-identity/deployment/src/main/java/io/quarkiverse/azure/identity/deployment/AzureIdentityProcessor.java b/internal/azure-identity/deployment/src/main/java/io/quarkiverse/azure/identity/deployment/AzureIdentityProcessor.java new file mode 100644 index 00000000..a15ff538 --- /dev/null +++ b/internal/azure-identity/deployment/src/main/java/io/quarkiverse/azure/identity/deployment/AzureIdentityProcessor.java @@ -0,0 +1,44 @@ +package io.quarkiverse.azure.identity.deployment; + +import java.util.stream.Stream; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.pkg.builditem.NativeImageRunnerBuildItem; +import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; + +public class AzureIdentityProcessor { + @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) + void runtimeInitializedClasses(BuildProducer runtimeInitializedClass) { + Stream.of( + "com.microsoft.aad.msal4jextensions.persistence.linux.ISecurityLibrary", + "com.microsoft.aad.msal4jextensions.persistence.mac.ISecurityLibrary") + .map(RuntimeInitializedClassBuildItem::new) + .forEach(runtimeInitializedClass::produce); + } + + @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) + public void resources( + BuildProducer resource, + NativeImageRunnerBuildItem nativeImageRunnerFactory) { + + // load native resources. + // https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/aot/azure-aot-graalvm-support/src/main/resources/META-INF/native-image/net.java.dev.jna/jna/resource-config.json + String dir = "QMETA-INF/services"; + String hotspotLibName = "jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory"; + String jvmCiServiceLocatorLibName = "jdk.vm.ci.services.JVMCIServiceLocator"; + String hotspotLibPath = dir + "/" + hotspotLibName; + String jvmCiServiceLocatorLibPath = dir + "/" + jvmCiServiceLocatorLibName; + + resource.produce(new NativeImageResourceBuildItem(hotspotLibPath)); + resource.produce(new NativeImageResourceBuildItem(jvmCiServiceLocatorLibPath)); + + String jnaDir = "Qcom/sun/jna/linux-x86-64"; + String jniLibName = "libjnidispatch.so"; + String jniLibPath = jnaDir + "/" + jniLibName; + + resource.produce(new NativeImageResourceBuildItem(jniLibPath)); + } +} diff --git a/internal/azure-identity/pom.xml b/internal/azure-identity/pom.xml new file mode 100644 index 00000000..fded28a8 --- /dev/null +++ b/internal/azure-identity/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-extensions + 999-SNAPSHOT + ../../extensions/pom.xml + + + quarkus-azure-identity-parent + Quarkus Azure Services :: Internal :: Azure Identity + pom + + + deployment + runtime + + diff --git a/internal/azure-identity/runtime/pom.xml b/internal/azure-identity/runtime/pom.xml new file mode 100644 index 00000000..e7a342ab --- /dev/null +++ b/internal/azure-identity/runtime/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + io.quarkiverse.azureservices + quarkus-azure-identity-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-azure-identity + Quarkus Azure Services :: Internal :: Azure Identity :: Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-netty + + + com.azure + azure-identity + + + com.azure + azure-core-http-netty + + + + + org.graalvm.sdk + graal-sdk + provided + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + \ No newline at end of file diff --git a/internal/azure-identity/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/internal/azure-identity/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..e3bf0b31 --- /dev/null +++ b/internal/azure-identity/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "Quarkus Support Azure Identity" +description: "Quarkus Support Azure Identity" +artifact: ${project.groupId}:${project.artifactId}:${project.version} +metadata: + icon-url: "https://learn.microsoft.com/en-us/media/logos/logo_azure.svg" + unlisted: true + keywords: + - "azure" + categories: + - "cloud" \ No newline at end of file diff --git a/internal/core/util/src/main/java/io/quarkiverse/azure/core/util/AzureQuarkusIdentifier.java b/internal/core/util/src/main/java/io/quarkiverse/azure/core/util/AzureQuarkusIdentifier.java index 4b70b5a2..4166e024 100644 --- a/internal/core/util/src/main/java/io/quarkiverse/azure/core/util/AzureQuarkusIdentifier.java +++ b/internal/core/util/src/main/java/io/quarkiverse/azure/core/util/AzureQuarkusIdentifier.java @@ -8,8 +8,11 @@ */ public final class AzureQuarkusIdentifier { + // length cannot be greater than 24 public static final String AZURE_QUARKUS_APP_CONFIGURATION = "az-qk-app-config"; public static final String AZURE_QUARKUS_STORAGE_BLOB = "az-qk-storage-blob"; + public static final String AZURE_QUARKUS_KEY_VAULT_ASYNC_CLIENT = "az-qk-kv-secret-async"; + public static final String AZURE_QUARKUS_KEY_VAULT_SYNC_CLIENT = "az-qk-kv-secret-sync"; private AzureQuarkusIdentifier() { diff --git a/internal/pom.xml b/internal/pom.xml index e43a62e6..2bef491d 100644 --- a/internal/pom.xml +++ b/internal/pom.xml @@ -14,6 +14,7 @@ pom + azure-identity core jackson-dataformat-xml http-client-vertx