This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7f2e58d6a94eb7747b83e34eb824916fd883caf8 Author: Andrea Cosentino <[email protected]> AuthorDate: Fri Apr 1 12:55:31 2022 +0200 CAMEL-17686 - Support ability to load properties from Vault/Secrets cloud services - Azure Key Vault --- .../org/apache/camel/properties-function/azure | 2 + .../key/vault/KeyVaultPropertiesFunction.java | 192 +++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/components/camel-azure/camel-azure-key-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/azure b/components/camel-azure/camel-azure-key-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/azure new file mode 100644 index 0000000..8536f13 --- /dev/null +++ b/components/camel-azure/camel-azure-key-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/azure @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.azure.key.vault.KeyVaultPropertiesFunction diff --git a/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java new file mode 100644 index 0000000..ea3e105 --- /dev/null +++ b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.azure.key.vault; + +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.security.keyvault.secrets.SecretClient; +import com.azure.security.keyvault.secrets.SecretClientBuilder; +import com.azure.security.keyvault.secrets.models.KeyVaultSecret; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.PropertiesFunction; +import org.apache.camel.support.service.ServiceSupport; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; +import org.apache.camel.vault.AzureVaultConfiguration; + +/** + * A {@link PropertiesFunction} that lookup the property value from Azure Key Vault service. + * <p/> + * The credentials to access Key vault is defined using three environment variables representing the static credentials: + * <ul> + * <li><tt>CAMEL_VAULT_AZURE_VAULT_NAME</tt></li> + * <li><tt>CAMEL_VAULT_AZURE_CLIENT_ID</tt></li> + * <li><tt>CAMEL_VAULT_AZURE_CLIENT_SECRET</tt></li> + * <li><tt>CAMEL_VAULT_AZURE_TENANT_ID</tt></li> + * </ul> + * <p/> + * + * Otherwise it is possible to specify the credentials as properties: + * + * <ul> + * <li><tt>camel.vault.azure.vaultName</tt></li> + * <li><tt>camel.vault.azure.clientId</tt></li> + * <li><tt>camel.vault.azure.clientSecret</tt></li> + * <li><tt>camel.vault.azure.tenantId</tt></li> + * </ul> + * <p/> + * + * This implementation is to return the secret value associated with a key. The properties related to this kind of + * Properties Function are all prefixed with <tt>azure:</tt>. For example asking for <tt>azure:token</tt>, will return + * the secret value associated to the secret named token on AWS Secrets Manager. + * + * Another way of retrieving a secret value is using the following notation <tt>azure:database/username</tt>: in this + * case the field username of the secret database will be returned. As a fallback, the user could provide a default + * value, which will be returned in case the secret doesn't exist, the secret has been marked for deletion or, for + * example, if a particular field of the secret doesn't exist. For using this feature, the user could use the following + * notation <tt>azure:database/username:admin</tt>. The admin value will be returned as default value, if the conditions + * above were all met. + */ + [email protected]("azure") +public class KeyVaultPropertiesFunction extends ServiceSupport implements PropertiesFunction, CamelContextAware { + + private static final String CAMEL_VAULT_AZURE_VAULT_NAME = "CAMEL_VAULT_AZURE_VAULT_NAME"; + private static final String CAMEL_VAULT_AZURE_CLIENT_ID = "CAMEL_VAULT_AZURE_CLIENT_ID"; + private static final String CAMEL_VAULT_AZURE_CLIENT_SECRET = "CAMEL_VAULT_AZURE_CLIENT_SECRET"; + private static final String CAMEL_VAULT_AZURE_TENANT_ID = "CAMEL_VAULT_AZURE_TENANT_ID"; + private CamelContext camelContext; + private SecretClient client; + + @Override + protected void doStart() throws Exception { + super.doStart(); + String vaultName = System.getenv(CAMEL_VAULT_AZURE_VAULT_NAME); + String clientId = System.getenv(CAMEL_VAULT_AZURE_CLIENT_ID); + String clientSecret = System.getenv(CAMEL_VAULT_AZURE_CLIENT_SECRET); + String tenantId = System.getenv(CAMEL_VAULT_AZURE_TENANT_ID); + if (ObjectHelper.isEmpty(vaultName) && ObjectHelper.isEmpty(clientId) && ObjectHelper.isEmpty(clientSecret) + && ObjectHelper.isEmpty(tenantId)) { + AzureVaultConfiguration azureVaultConfiguration = getCamelContext().getVaultConfiguration().azure(); + if (ObjectHelper.isNotEmpty(azureVaultConfiguration)) { + vaultName = azureVaultConfiguration.getVaultName(); + clientId = azureVaultConfiguration.getClientId(); + clientSecret = azureVaultConfiguration.getClientSecret(); + tenantId = azureVaultConfiguration.getTenantId(); + } + } + if (ObjectHelper.isNotEmpty(vaultName) && ObjectHelper.isNotEmpty(clientId) && ObjectHelper.isNotEmpty(clientSecret) + && ObjectHelper.isNotEmpty(tenantId)) { + String keyVaultUri = "https://" + vaultName + ".vault.azure.net"; + + // Credential + ClientSecretCredential credential = new ClientSecretCredentialBuilder() + .tenantId(tenantId) + .clientId(clientId) + .clientSecret(clientSecret) + .build(); + + // Build Client + client = new SecretClientBuilder() + .vaultUrl(keyVaultUri) + .credential(credential) + .buildClient(); + } else { + throw new RuntimeCamelException( + "Using the Azure Key Vault Properties Function requires setting Azure credentials as application properties or environment variables"); + } + } + + @Override + public String getName() { + return "azure"; + } + + @Override + public String apply(String remainder) { + String key = remainder; + String subkey = null; + String returnValue = null; + String defaultValue = null; + if (remainder.contains("/")) { + key = StringHelper.before(remainder, "/"); + subkey = StringHelper.after(remainder, "/"); + defaultValue = StringHelper.after(subkey, ":"); + if (subkey.contains(":")) { + subkey = StringHelper.before(subkey, ":"); + } + } else if (remainder.contains(":")) { + key = StringHelper.before(remainder, ":"); + defaultValue = StringHelper.after(remainder, ":"); + } + + if (key != null) { + try { + returnValue = getSecretFromSource(key, subkey, defaultValue); + } catch (JsonProcessingException e) { + throw new RuntimeCamelException("Something went wrong while recovering " + key + " from vault"); + } + } + + return returnValue; + } + + private String getSecretFromSource( + String key, String subkey, String defaultValue) + throws JsonProcessingException { + String returnValue; + try { + KeyVaultSecret secret = client.getSecret(key); + returnValue = secret.getValue(); + if (ObjectHelper.isNotEmpty(subkey)) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode actualObj = mapper.readTree(returnValue); + JsonNode field = actualObj.get(subkey); + if (ObjectHelper.isNotEmpty(field)) { + returnValue = field.textValue(); + } else { + returnValue = null; + } + } + if (ObjectHelper.isEmpty(returnValue)) { + returnValue = defaultValue; + } + } catch (Exception ex) { + if (ObjectHelper.isNotEmpty(defaultValue)) { + returnValue = defaultValue; + } else { + throw ex; + } + } + return returnValue; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } +}
