This is an automated email from the ASF dual-hosted git repository. sanjeevrk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push: new 1aff3c4 Secretprovider interfaces and some default implementations (#2855) 1aff3c4 is described below commit 1aff3c4cdc60ec81b0230289944a97cb1db4d683 Author: Sanjeev Kulkarni <sanjee...@gmail.com> AuthorDate: Sun Oct 28 07:59:03 2018 -0700 Secretprovider interfaces and some default implementations (#2855) * Added SecretProvider and SecretProviderConfigurator interfaces and some implementations * Added appropriate documentation * Added validation logic to config provider configurator * Added more comments * Took feedback into account --- .../pulsar/common/functions/FunctionConfig.java | 3 +- .../org/apache/pulsar/common/io/SinkConfig.java | 3 +- .../org/apache/pulsar/common/io/SourceConfig.java | 3 +- pulsar-functions/pom.xml | 1 + pulsar-functions/{ => secrets}/pom.xml | 54 ++++------ .../secretsprovider/ClearTextSecretsProvider.java | 38 +++++++ .../EnvironmentBasedSecretsProvider.java | 35 +++++++ .../functions/secretsprovider/SecretsProvider.java | 40 ++++++++ .../DefaultSecretsProviderConfigurator.java | 67 ++++++++++++ .../KubernetesSecretsProviderConfigurator.java | 113 +++++++++++++++++++++ .../SecretsProviderConfigurator.java | 72 +++++++++++++ .../ClearTextSecretsProviderTest.java | 37 +++++++ .../EnvironmentBasedSecretsProviderTest.java | 75 ++++++++++++++ .../KubernetesSecretsProviderConfiguratorTest.java | 62 +++++++++++ 14 files changed, 566 insertions(+), 37 deletions(-) diff --git a/pulsar-common/src/main/java/org/apache/pulsar/common/functions/FunctionConfig.java b/pulsar-common/src/main/java/org/apache/pulsar/common/functions/FunctionConfig.java index 2163df1..28a15af 100644 --- a/pulsar-common/src/main/java/org/apache/pulsar/common/functions/FunctionConfig.java +++ b/pulsar-common/src/main/java/org/apache/pulsar/common/functions/FunctionConfig.java @@ -82,7 +82,8 @@ public class FunctionConfig { // This is a map of secretName(aka how the secret is going to be // accessed in the function via context) to an object that // encapsulates how the secret is fetched by the underlying - // secrets provider + // secrets provider. The type of an value here can be found by the + // SecretProviderConfigurator.getSecretObjectType() method. private Map<String, Object> secrets; private Runtime runtime; private boolean autoAck; diff --git a/pulsar-common/src/main/java/org/apache/pulsar/common/io/SinkConfig.java b/pulsar-common/src/main/java/org/apache/pulsar/common/io/SinkConfig.java index 355c696..3eacfbc 100644 --- a/pulsar-common/src/main/java/org/apache/pulsar/common/io/SinkConfig.java +++ b/pulsar-common/src/main/java/org/apache/pulsar/common/io/SinkConfig.java @@ -58,7 +58,8 @@ public class SinkConfig { // This is a map of secretName(aka how the secret is going to be // accessed in the function via context) to an object that // encapsulates how the secret is fetched by the underlying - // secrets provider + // secrets provider. The type of an value here can be found by the + // SecretProviderConfigurator.getSecretObjectType() method. private Map<String, Object> secrets; private int parallelism = 1; private FunctionConfig.ProcessingGuarantees processingGuarantees; diff --git a/pulsar-common/src/main/java/org/apache/pulsar/common/io/SourceConfig.java b/pulsar-common/src/main/java/org/apache/pulsar/common/io/SourceConfig.java index 9dbe97c..c1b2efe 100644 --- a/pulsar-common/src/main/java/org/apache/pulsar/common/io/SourceConfig.java +++ b/pulsar-common/src/main/java/org/apache/pulsar/common/io/SourceConfig.java @@ -49,7 +49,8 @@ public class SourceConfig { // This is a map of secretName(aka how the secret is going to be // accessed in the function via context) to an object that // encapsulates how the secret is fetched by the underlying - // secrets provider + // secrets provider. The type of an value here can be found by the + // SecretProviderConfigurator.getSecretObjectType() method. private Map<String, Object> secrets; private int parallelism = 1; private FunctionConfig.ProcessingGuarantees processingGuarantees; diff --git a/pulsar-functions/pom.xml b/pulsar-functions/pom.xml index f54effd..a27c21b 100644 --- a/pulsar-functions/pom.xml +++ b/pulsar-functions/pom.xml @@ -43,6 +43,7 @@ <module>runtime-shaded</module> <module>runtime-all</module> <module>worker</module> + <module>secrets</module> </modules> <dependencyManagement> diff --git a/pulsar-functions/pom.xml b/pulsar-functions/secrets/pom.xml similarity index 53% copy from pulsar-functions/pom.xml copy to pulsar-functions/secrets/pom.xml index f54effd..fa411d7 100644 --- a/pulsar-functions/pom.xml +++ b/pulsar-functions/secrets/pom.xml @@ -19,49 +19,35 @@ --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <packaging>pom</packaging> <parent> <groupId>org.apache.pulsar</groupId> - <artifactId>pulsar</artifactId> + <artifactId>pulsar-functions</artifactId> <version>2.3.0-SNAPSHOT</version> </parent> - <artifactId>pulsar-functions</artifactId> - <name>Pulsar Functions :: Parent</name> - - <modules> - <module>proto</module> - <module>proto-shaded</module> - <module>api-java</module> - <module>java-examples</module> - <module>utils</module> - <module>metrics</module> - <module>instance</module> - <module>runtime</module> - <module>runtime-shaded</module> - <module>runtime-all</module> - <module>worker</module> - </modules> - - <dependencyManagement> - <dependencies> - - <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>1.16.12</version> - </dependency> - - </dependencies> - </dependencyManagement> + <artifactId>pulsar-functions-secrets</artifactId> + <name>Pulsar Functions :: Secrets</name> <dependencies> + <dependency> + <groupId>io.kubernetes</groupId> + <artifactId>client-java</artifactId> + <version>2.0.0</version> + <scope>compile</scope> + <exclusions> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <scope>provided</scope> + <groupId>org.apache.pulsar</groupId> + <artifactId>pulsar-functions-proto</artifactId> + <version>${project.version}</version> </dependency> </dependencies> diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProvider.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProvider.java new file mode 100644 index 0000000..adb2852 --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProvider.java @@ -0,0 +1,38 @@ +/** + * 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.pulsar.functions.secretsprovider; + +/** + * This file defines a very basic clear text secrets provider which treats + * the secrets as being passed in cleartext. + */ +public class ClearTextSecretsProvider implements SecretsProvider { + /** + * Fetches a secret + * @return The actual secret + */ + @Override + public String provideSecret(String secretName, Object pathToSecret) { + if (pathToSecret != null) { + return pathToSecret.toString(); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProvider.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProvider.java new file mode 100644 index 0000000..b058709 --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProvider.java @@ -0,0 +1,35 @@ +/** + * 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.pulsar.functions.secretsprovider; + +/** + * This defines a very simple Secrets Provider that looks up environment variable + * thats named the same as secretName and fetches it. + */ +public class EnvironmentBasedSecretsProvider implements SecretsProvider { + + /** + * Fetches a secret + * @return The actual secret + */ + @Override + public String provideSecret(String secretName, Object pathToSecret) { + return System.getenv(secretName); + } +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/SecretsProvider.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/SecretsProvider.java new file mode 100644 index 0000000..7d5330d --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsprovider/SecretsProvider.java @@ -0,0 +1,40 @@ +/** + * 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.pulsar.functions.secretsprovider; + +import java.util.Map; + +/** + * This file defines the SecretsProvider interface. This interface is used by the function + * instances/containers to actually fetch the secrets. What SecretsProvider to use is + * decided by the SecretsProviderConfigurator + */ +public interface SecretsProvider { + /** + * Initialize the SecretsProvider + * @return + */ + default void init(Map<String, String> config) {} + + /** + * Fetches a secret + * @return The actual secret + */ + String provideSecret(String secretName, Object pathToSecret); +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/DefaultSecretsProviderConfigurator.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/DefaultSecretsProviderConfigurator.java new file mode 100644 index 0000000..10f9d54 --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/DefaultSecretsProviderConfigurator.java @@ -0,0 +1,67 @@ +/** + * 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.pulsar.functions.secretsproviderconfigurator; + +import com.google.gson.reflect.TypeToken; +import io.kubernetes.client.models.V1Container; +import org.apache.pulsar.functions.proto.Function; +import org.apache.pulsar.functions.secretsprovider.ClearTextSecretsProvider; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * This is a barebones version of a secrets provider which wires in ClearTextSecretsProvider + * to the function instances/containers. + * While this is the default configurator, it is highly recommended that for real-security + * you use some alternate provider. + */ +public class DefaultSecretsProviderConfigurator implements SecretsProviderConfigurator { + @Override + public String getSecretsProviderClassName(Function.FunctionDetails functionDetails) { + switch (functionDetails.getRuntime()) { + case JAVA: + return ClearTextSecretsProvider.class.getName(); + case PYTHON: + return "secretsprovider.ClearTextSecretsProvider"; + default: + throw new RuntimeException("Unknwon runtime " + functionDetails.getRuntime()); + } + } + + @Override + public Map<String, String> getSecretsProviderConfig(Function.FunctionDetails functionDetails) { + return null; + } + + @Override + public void configureKubernetesRuntimeSecretsProvider(V1Container container, Function.FunctionDetails functionDetails) { + // noop + } + + @Override + public void configureProcessRuntimeSecretsProvider(ProcessBuilder processBuilder, Function.FunctionDetails functionDetails) { + // noop + } + + @Override + public Type getSecretObjectType() { + return new TypeToken<String>() {}.getType(); + } +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfigurator.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfigurator.java new file mode 100644 index 0000000..0321734 --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfigurator.java @@ -0,0 +1,113 @@ +/** + * 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.pulsar.functions.secretsproviderconfigurator; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.kubernetes.client.models.V1Container; +import io.kubernetes.client.models.V1EnvVar; +import io.kubernetes.client.models.V1EnvVarSource; +import io.kubernetes.client.models.V1SecretKeySelector; +import org.apache.commons.lang3.StringUtils; +import org.apache.pulsar.functions.proto.Function; +import org.apache.pulsar.functions.secretsprovider.EnvironmentBasedSecretsProvider; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * This file defines the SecretsProviderConfigurator that will be used by default for running in Kubernetes. + * As such this implementation is strictly when workers are configured to use kubernetes runtime. + * We use kubernetes in built secrets and bind them as environment variables within the function container + * to ensure that the secrets are availble to the function at runtime. Then we plug in the + * EnvironmentBasedSecretsConfig as the secrets provider who knows how to read these environment variables + */ +public class KubernetesSecretsProviderConfigurator implements SecretsProviderConfigurator { + private static String ID_KEY = "id"; + private static String KEY_KEY = "key"; + @Override + public String getSecretsProviderClassName(Function.FunctionDetails functionDetails) { + switch (functionDetails.getRuntime()) { + case JAVA: + return EnvironmentBasedSecretsProvider.class.getName(); + case PYTHON: + return "secretsprovider.EnvironmentBasedSecretsProvider"; + default: + throw new RuntimeException("Unknown function runtime " + functionDetails.getRuntime()); + } + } + + @Override + public Map<String, String> getSecretsProviderConfig(Function.FunctionDetails functionDetails) { + return null; + } + + // Kubernetes secrets can be exposed as volume mounts or as environment variables in the pods. We are currently using the + // environment variables way. Essentially the secretName/secretPath is attached as secretRef to the environment variables + // of a pod and kubernetes magically makes the secret pointed to by this combination available as a env variable. + @Override + public void configureKubernetesRuntimeSecretsProvider(V1Container container, Function.FunctionDetails functionDetails) { + if (!StringUtils.isEmpty(functionDetails.getSecretsMap())) { + Type type = new TypeToken<Map<String, Object>>() { + }.getType(); + Map<String, Object> secretsMap = new Gson().fromJson(functionDetails.getSecretsMap(), type); + for (Map.Entry<String, Object> entry : secretsMap.entrySet()) { + final V1EnvVar secretEnv = new V1EnvVar(); + Map<String, String> kv = (Map<String, String>) entry.getValue(); + secretEnv.name(entry.getKey()) + .valueFrom(new V1EnvVarSource() + .secretKeyRef(new V1SecretKeySelector() + .name(kv.get(ID_KEY)) + .key(kv.get(KEY_KEY)))); + container.addEnvItem(secretEnv); + } + } + } + + @Override + public void configureProcessRuntimeSecretsProvider(ProcessBuilder processBuilder, Function.FunctionDetails functionDetails) { + throw new RuntimeException("KubernetesSecretsProviderConfigurator should only be setup for Kubernetes Runtime"); + } + + @Override + public Type getSecretObjectType() { + return new TypeToken<Map<String, String>>() {}.getType(); + } + + // The secret object should be of type Map<String, String> and it should contain "id" and "key" + @Override + public void validateSecretMap(Map<String, Object> secretMap) { + for (Object object : secretMap.values()) { + if (object instanceof Map) { + Map<String, String> kubernetesSecret = (Map<String, String>) object; + if (kubernetesSecret.size() < 2) { + throw new IllegalArgumentException("Kubernetes Secret should contain id and key"); + } + if (!kubernetesSecret.containsKey(ID_KEY)) { + throw new IllegalArgumentException("Kubernetes Secret should contain id information"); + } + if (!kubernetesSecret.containsKey(KEY_KEY)) { + throw new IllegalArgumentException("Kubernetes Secret should contain key information"); + } + } else { + throw new IllegalArgumentException("Kubernetes Secret should be a Map containing id/key pairs"); + } + } + } +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/SecretsProviderConfigurator.java b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/SecretsProviderConfigurator.java new file mode 100644 index 0000000..a1792f1 --- /dev/null +++ b/pulsar-functions/secrets/src/main/java/org/apache/pulsar/functions/secretsproviderconfigurator/SecretsProviderConfigurator.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.pulsar.functions.secretsproviderconfigurator; + +import io.kubernetes.client.models.V1Container; +import org.apache.pulsar.functions.proto.Function; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * This file defines the SecretsProviderConfigurator interface. This interface is used by the function_workers + * to choose the SecretProvider class name(if any) and its associated config at the time of starting + * the function instances. + */ +public interface SecretsProviderConfigurator { + /** + * Initialize the SecretsProviderConfigurator + * @return + */ + default void init(Map<String, String> config) {} + + /** + * Return the Secrets Provider Classname. This will be passed to the cmdline + * of the instance and should contain the logic of connecting with the secrets + * provider and obtaining secrets + */ + String getSecretsProviderClassName(Function.FunctionDetails functionDetails); + + /** + * Return the secrets provider config + */ + Map<String, String> getSecretsProviderConfig(Function.FunctionDetails functionDetails); + + /** + * Attaches any secrets specific stuff to the k8 container for kubernetes runtime + */ + void configureKubernetesRuntimeSecretsProvider(V1Container container, Function.FunctionDetails functionDetails); + + /** + * Attaches any secrets specific stuff to the ProcessBuilder for process runtime + */ + void configureProcessRuntimeSecretsProvider(ProcessBuilder processBuilder, Function.FunctionDetails functionDetails); + + /** + * What is the type of the object that should be in the user secret config + * @return + */ + Type getSecretObjectType(); + + /** + * Do config checks to see whether the secrets provided are conforming + */ + default void validateSecretMap(Map<String, Object> secretMap) {} + +} \ No newline at end of file diff --git a/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProviderTest.java b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProviderTest.java new file mode 100644 index 0000000..d6f0139 --- /dev/null +++ b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/ClearTextSecretsProviderTest.java @@ -0,0 +1,37 @@ +/** + * 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.pulsar.functions.secretsprovider; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Unit test of {@link Exceptions}. + */ +public class ClearTextSecretsProviderTest { + + @Test + public void testConfigValidation() throws Exception { + ClearTextSecretsProvider provider = new ClearTextSecretsProvider(); + Assert.assertEquals(provider.provideSecret("SecretName", "SecretValue"), "SecretValue"); + Assert.assertEquals(provider.provideSecret("SecretName", ""), ""); + Assert.assertEquals(provider.provideSecret("SecretName", null), null); + } +} diff --git a/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProviderTest.java b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProviderTest.java new file mode 100644 index 0000000..fa80d70 --- /dev/null +++ b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsprovider/EnvironmentBasedSecretsProviderTest.java @@ -0,0 +1,75 @@ +/** + * 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.pulsar.functions.secretsprovider; + +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.lang.reflect.Field; +import java.util.Map; + +import static org.mockito.Matchers.anyString; + +/** + * Unit test of {@link Exceptions}. + */ +public class EnvironmentBasedSecretsProviderTest { + @Test + public void testConfigValidation() throws Exception { + EnvironmentBasedSecretsProvider provider = new EnvironmentBasedSecretsProvider(); + Assert.assertEquals(provider.provideSecret("mySecretName", "Ignored"), null); + injectEnvironmentVariable("mySecretName", "SecretValue"); + Assert.assertEquals(provider.provideSecret("mySecretName", "Ignored"), "SecretValue"); + } + + private static void injectEnvironmentVariable(String key, String value) + throws Exception { + + Class<?> processEnvironment = Class.forName("java.lang.ProcessEnvironment"); + + Field unmodifiableMapField = getAccessibleField(processEnvironment, "theUnmodifiableEnvironment"); + Object unmodifiableMap = unmodifiableMapField.get(null); + injectIntoUnmodifiableMap(key, value, unmodifiableMap); + + Field mapField = getAccessibleField(processEnvironment, "theEnvironment"); + Map<String, String> map = (Map<String, String>) mapField.get(null); + map.put(key, value); + } + + private static Field getAccessibleField(Class<?> clazz, String fieldName) + throws NoSuchFieldException { + + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } + + private static void injectIntoUnmodifiableMap(String key, String value, Object map) + throws ReflectiveOperationException { + + Class unmodifiableMap = Class.forName("java.util.Collections$UnmodifiableMap"); + Field field = getAccessibleField(unmodifiableMap, "m"); + Object obj = field.get(map); + ((Map<String, String>) obj).put(key, value); + } +} diff --git a/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfiguratorTest.java b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfiguratorTest.java new file mode 100644 index 0000000..6d64d5e --- /dev/null +++ b/pulsar-functions/secrets/src/test/java/org/apache/pulsar/functions/secretsproviderconfigurator/KubernetesSecretsProviderConfiguratorTest.java @@ -0,0 +1,62 @@ +/** + * 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.pulsar.functions.secretsproviderconfigurator; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.HashMap; + +/** + * Unit test of {@link Exceptions}. + */ +public class KubernetesSecretsProviderConfiguratorTest { + + @Test + public void testConfigValidation() throws Exception { + KubernetesSecretsProviderConfigurator provider = new KubernetesSecretsProviderConfigurator(); + try { + HashMap<String, Object> map = new HashMap<String, Object>(); + map.put("secretname", "randomsecret"); + provider.validateSecretMap(map); + Assert.fail("Non conforming secret object should not validate"); + } catch (Exception e) { + } + try { + HashMap<String, Object> map = new HashMap<String, Object>(); + HashMap<String, String> map1 = new HashMap<String, String>(); + map1.put("secretname", "secretvalue"); + map.put("secretname", map1); + provider.validateSecretMap(map); + Assert.fail("Non conforming secret object should not validate"); + } catch (Exception e) { + } + try { + HashMap<String, Object> map = new HashMap<String, Object>(); + HashMap<String, String> map1 = new HashMap<String, String>(); + map1.put("id", "secretvalue"); + map1.put("key", "secretvalue"); + map.put("secretname", map1); + provider.validateSecretMap(map); + } catch (Exception e) { + Assert.fail("Conforming secret object should validate"); + } + } +}