srkukarni closed pull request #2855: Secretprovider interfaces and some default implementations URL: https://github.com/apache/pulsar/pull/2855
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): 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 2163df1db9..28a15af4d1 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 @@ // 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 355c6962e5..3eacfbcaef 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 @@ // 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 9dbe97c3cd..c1b2efea98 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 @@ // 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 f54effdb13..a27c21b387 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/secrets/pom.xml b/pulsar-functions/secrets/pom.xml new file mode 100644 index 0000000000..fa411d7d1c --- /dev/null +++ b/pulsar-functions/secrets/pom.xml @@ -0,0 +1,54 @@ +<!-- + + 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. + +--> +<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"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.pulsar</groupId> + <artifactId>pulsar-functions</artifactId> + <version>2.3.0-SNAPSHOT</version> + </parent> + + <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.apache.pulsar</groupId> + <artifactId>pulsar-functions-proto</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> 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 0000000000..adb285287a --- /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 0000000000..b058709741 --- /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 0000000000..7d5330dfd0 --- /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 0000000000..10f9d546e4 --- /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 0000000000..0321734a38 --- /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 0000000000..a1792f1e3a --- /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 0000000000..d6f01395a3 --- /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 0000000000..fa80d70486 --- /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 0000000000..6d64d5e913 --- /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"); + } + } +} ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services