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 c7ca25455cdecc8cda04b79aa283499e53077098
Author: Andrea Cosentino <anco...@gmail.com>
AuthorDate: Wed Jul 13 19:13:40 2022 +0200

    CAMEL-17688 - Support ability to load properties from Vault/Secrets cloud 
services - Hashicorp Vault
---
 .../org/apache/camel/properties-function/hashicorp |   2 +
 .../vault/HashicorpVaultPropertiesFunction.java    | 221 +++++++++++++++++++++
 .../HashicorpVaultPropertiesSourceTestIT.java      |  72 +++++++
 3 files changed, 295 insertions(+)

diff --git 
a/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/hashicorp
 
b/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/hashicorp
new file mode 100644
index 00000000000..1bc4dea7180
--- /dev/null
+++ 
b/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/properties-function/hashicorp
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction
diff --git 
a/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
 
b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
new file mode 100644
index 00000000000..673a94e956a
--- /dev/null
+++ 
b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
@@ -0,0 +1,221 @@
+/*
+ * 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.hashicorp.vault;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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.HashicorpVaultConfiguration;
+import org.springframework.vault.authentication.TokenAuthentication;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
+
+/**
+ * A {@link PropertiesFunction} that lookup the property value from AWS 
Secrets Manager service.
+ * <p/>
+ * The credentials to access Secrets Manager is defined using three 
environment variables representing the static
+ * credentials:
+ * <ul>
+ * <li><tt>CAMEL_VAULT_AWS_ACCESS_KEY</tt></li>
+ * <li><tt>CAMEL_VAULT_AWS_SECRET_KEY</tt></li>
+ * <li><tt>CAMEL_VAULT_AWS_REGION</tt></li>
+ * <li><tt>CAMEL_VAULT_AWS_USE_DEFAULT_CREDENTIALS_PROVIDER</tt></li>
+ * </ul>
+ * <p/>
+ *
+ * Otherwise it is possible to specify the credentials as properties:
+ *
+ * <ul>
+ * <li><tt>camel.vault.aws.accessKey</tt></li>
+ * <li><tt>camel.vault.aws.secretKey</tt></li>
+ * <li><tt>camel.vault.aws.region</tt></li>
+ * <li><tt>camel.vault.aws.useDefaultCredentialsProvider</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>aws:</tt>. For example asking 
for <tt>aws: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>aws: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>aws:database/username:admin</tt>. The admin value will be returned as 
default value, if the conditions above were
+ * all met.
+ */
+
+@org.apache.camel.spi.annotations.PropertiesFunction("hashicorp")
+public class HashicorpVaultPropertiesFunction extends ServiceSupport 
implements PropertiesFunction, CamelContextAware {
+
+    private static final String CAMEL_HASHICORP_VAULT_TOKEN_ENV = 
"CAMEL_HASHICORP_VAULT_TOKEN_ENV";
+    private static final String CAMEL_HASHICORP_VAULT_ENGINE_ENV = 
"CAMEL_HASHICORP_VAULT_ENGINE_ENV";
+    private static final String CAMEL_HASHICORP_VAULT_HOST_ENV = 
"CAMEL_HASHICORP_VAULT_HOST_ENV";
+    private static final String CAMEL_HASHICORP_VAULT_PORT_ENV
+            = "CAMEL_HASHICORP_VAULT_PORT_ENV";
+    private static final String CAMEL_HASHICORP_VAULT_SCHEME_ENV
+            = "CAMEL_HASHICORP_VAULT_SCHEME_ENV";
+    private CamelContext camelContext;
+    private VaultTemplate client;
+
+    protected String engine;
+
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+        String token = System.getenv(CAMEL_HASHICORP_VAULT_TOKEN_ENV);
+        engine = System.getenv(CAMEL_HASHICORP_VAULT_ENGINE_ENV);
+        String host = System.getenv(CAMEL_HASHICORP_VAULT_HOST_ENV);
+        String port = System.getenv(CAMEL_HASHICORP_VAULT_PORT_ENV);
+        String scheme = System.getenv(CAMEL_HASHICORP_VAULT_SCHEME_ENV);
+        if (ObjectHelper.isEmpty(token) && ObjectHelper.isEmpty(engine) && 
ObjectHelper.isEmpty(host) && ObjectHelper.isEmpty(port) && 
ObjectHelper.isEmpty(scheme)) {
+            HashicorpVaultConfiguration hashicorpVaultConfiguration = 
getCamelContext().getVaultConfiguration().hashicorp();
+            if (ObjectHelper.isNotEmpty(hashicorpVaultConfiguration)) {
+                token = hashicorpVaultConfiguration.getToken();
+                engine = hashicorpVaultConfiguration.getEngine();
+                host = hashicorpVaultConfiguration.getHost();
+                port = hashicorpVaultConfiguration.getPort();
+                scheme = hashicorpVaultConfiguration.getScheme();
+            }
+        }
+        if (ObjectHelper.isNotEmpty(token) && ObjectHelper.isNotEmpty(engine) 
&& ObjectHelper.isNotEmpty(host) && ObjectHelper.isNotEmpty(port) && 
ObjectHelper.isNotEmpty(scheme)) {
+            VaultEndpoint vaultEndpoint = new VaultEndpoint();
+            vaultEndpoint.setHost(host);
+            vaultEndpoint.setPort(Integer.parseInt(port));
+            vaultEndpoint.setScheme(scheme);
+
+            client = new VaultTemplate(
+                    vaultEndpoint,
+                    new TokenAuthentication(token));
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the Hashicorp Properties Function requires setting 
Engine, Token, Host, port and scheme properties");
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+    }
+
+    @Override
+    public String getName() {
+        return "hashicorp";
+    }
+
+    @Override
+    public String apply(String remainder) {
+        String key = remainder;
+        String subkey = null;
+        String returnValue = null;
+        String defaultValue = null;
+        String version = null;
+        if (remainder.contains("/")) {
+            key = StringHelper.before(remainder, "/");
+            subkey = StringHelper.after(remainder, "/");
+            defaultValue = StringHelper.after(subkey, ":");
+            if (ObjectHelper.isNotEmpty(defaultValue)) {
+                if (defaultValue.contains("@")) {
+                    version = StringHelper.after(defaultValue, "@");
+                    defaultValue = StringHelper.before(defaultValue, "@");
+                }
+            }
+            if (subkey.contains(":")) {
+                subkey = StringHelper.before(subkey, ":");
+            }
+            if (subkey.contains("@")) {
+                version = StringHelper.after(subkey, "@");
+                subkey = StringHelper.before(subkey, "@");
+            }
+        } else if (remainder.contains(":")) {
+            key = StringHelper.before(remainder, ":");
+            defaultValue = StringHelper.after(remainder, ":");
+            if (remainder.contains("@")) {
+                version = StringHelper.after(remainder, "@");
+                defaultValue = StringHelper.before(defaultValue, "@");
+            }
+        } else {
+            if (remainder.contains("@")) {
+                key = StringHelper.before(remainder, "@");
+                version = StringHelper.after(remainder, "@");
+            }
+        }
+
+        if (key != null) {
+            try {
+                returnValue = getSecretFromSource(key, subkey, defaultValue, 
version);
+            } 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, String version)
+            throws JsonProcessingException {
+        String returnValue = null;
+        try {
+            String completePath = engine + "/" + "data" + "/" + key;
+            if (ObjectHelper.isNotEmpty(version)) {
+                completePath = completePath + "?version=" + version;
+            }
+            VaultResponse rawSecret = client.read(completePath);
+            if (ObjectHelper.isNotEmpty(rawSecret)) {
+                returnValue = rawSecret.getData().get("data").toString();
+            }
+            if (ObjectHelper.isNotEmpty(subkey)) {
+                Object field = ((Map) 
rawSecret.getData().get("data")).get(subkey);
+                if (ObjectHelper.isNotEmpty(field)) {
+                    returnValue = field.toString();
+                } 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;
+    }
+}
+
diff --git 
a/components/camel-hashicorp-vault/src/test/java/org/apache/camel/component/hashicorp/vault/integration/HashicorpVaultPropertiesSourceTestIT.java
 
b/components/camel-hashicorp-vault/src/test/java/org/apache/camel/component/hashicorp/vault/integration/HashicorpVaultPropertiesSourceTestIT.java
new file mode 100644
index 00000000000..beacf7b95cc
--- /dev/null
+++ 
b/components/camel-hashicorp-vault/src/test/java/org/apache/camel/component/hashicorp/vault/integration/HashicorpVaultPropertiesSourceTestIT.java
@@ -0,0 +1,72 @@
+/*
+ * 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.hashicorp.vault.integration;
+
+import org.apache.camel.FailedToCreateRouteException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class HashicorpVaultPropertiesSourceTestIT extends CamelTestSupport {
+
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_TOKEN_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_ENGINE_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_HOST_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_PORT_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_SCHEME_ENV", 
matches = ".*")
+    @Test
+    public void testFunctio() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:start").setBody(simple("{{hashicorp:hello}}")).to("mock:bar");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:bar").expectedBodiesReceived("{id=21}");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_TOKEN_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_ENGINE_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_HOST_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_PORT_ENV", 
matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "CAMEL_HASHICORP_VAULT_SCHEME_ENV", 
matches = ".*")
+    @Test
+    public void testFunctionWithField() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:start").setBody(simple("{{hashicorp:hello/id}}")).to("mock:bar");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:bar").expectedBodiesReceived("21");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+}

Reply via email to