You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by da...@apache.org on 2019/07/26 08:21:10 UTC

svn commit: r1863778 - in /felix/trunk/configadmin-plugins: ./ substitution/ substitution/src/ substitution/src/main/ substitution/src/main/java/ substitution/src/main/java/org/ substitution/src/main/java/org/apache/ substitution/src/main/java/org/apac...

Author: davidb
Date: Fri Jul 26 08:21:10 2019
New Revision: 1863778

URL: http://svn.apache.org/viewvc?rev=1863778&view=rev
Log:
ConfigAdmin plugin that can substitute variable placeholders (e.g. for K8s secrets)

Initial implementation, including README.md and test

Added:
    felix/trunk/configadmin-plugins/
    felix/trunk/configadmin-plugins/substitution/
    felix/trunk/configadmin-plugins/substitution/README.md
    felix/trunk/configadmin-plugins/substitution/pom.xml
    felix/trunk/configadmin-plugins/substitution/src/
    felix/trunk/configadmin-plugins/substitution/src/main/
    felix/trunk/configadmin-plugins/substitution/src/main/java/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/Activator.java
    felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPlugin.java
    felix/trunk/configadmin-plugins/substitution/src/test/
    felix/trunk/configadmin-plugins/substitution/src/test/java/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/ActivatorTest.java
    felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPluginTest.java
    felix/trunk/configadmin-plugins/substitution/src/test/resources/
    felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/
    felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/
    felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/testfile2
    felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile
    felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile.txt

Added: felix/trunk/configadmin-plugins/substitution/README.md
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/README.md?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/README.md (added)
+++ felix/trunk/configadmin-plugins/substitution/README.md Fri Jul 26 08:21:10 2019
@@ -0,0 +1,45 @@
+# org.apache.felix.configadmin.plugin.substitution
+
+An OSGi Configuration Admin Plugin that can substitute values in configuration with values obtained elsewhere. Supported sources:
+
+* Files on disk, for example to be used with Kubernetes secrets
+
+## Usage with Kubernetes secrets
+
+The Kubernetes secrets will surface as file at a certain mountpoint, e.g.:
+
+```
+$ ls /mnt/mysecrets
+db.password
+my.api.key
+another.secret
+```
+
+The file contents are opaque and contain the secret value as-is.
+
+This plugin will replace OSGi Configuration Admin values declared
+using secrets placeholders. These placeholders start with `$[secret:`
+and end with `]`. The word inside the placeholder identifies the secret
+name as found in the secrets directory on the filesystem. For example:
+
+```
+com.my.database:
+"user": "my-user",
+"password": "$[secret:db.password]"
+```
+
+In this example the `user` name for the database is left as-is but for the
+`password` the content of the `/mnt/mysecrets/db.password` file is used.
+
+## Configuration
+
+The plugin needs to be provided with the directory where the secrets can be
+found on the local filesystem.
+
+This is done through the following property:
+
+* `org.apache.felix.configadmin.plugin.substitution.dir`: specify the directory where the Kubernetes secrets are mounted.
+
+The property can be provided as an OSGi Framework property or alternatively as a Java System Property. 
+
+If configuration is not provided the plugin will be disabled.

Added: felix/trunk/configadmin-plugins/substitution/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/pom.xml?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/pom.xml (added)
+++ felix/trunk/configadmin-plugins/substitution/pom.xml Fri Jul 26 08:21:10 2019
@@ -0,0 +1,125 @@
+<!--
+    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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>6</version>
+        <relativePath>../../../pom/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.felix.configadmin.plugin.substitution</artifactId>
+    <packaging>jar</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Felix Configuration Admin Values Substitution Plugin</name>
+    <description>
+            This plugin makes it possible to substitute placeholder values in ConfigAdmin configuration with values from the 
+            file system, environment variables or system properties.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/configadmin-plugins/substitution</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/configadmin-plugins/substitution</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
+                <version>4.2.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>bnd-process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <bnd><![CDATA[
+                        Bundle-Activator: org.apache.felix.configadminplugin.substitution.Activator
+                        -snapshot: SNAPSHOT
+                    ]]></bnd>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/test/resources/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <version>7.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <version>7.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.26</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.26</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

Added: felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/Activator.java?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/Activator.java (added)
+++ felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/Activator.java Fri Jul 26 08:21:10 2019
@@ -0,0 +1,53 @@
+/*
+ * 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.felix.configadmin.plugin.substitution;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationPlugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+public class Activator implements BundleActivator {
+    static final String DIR_PROPERTY = "org.apache.felix.configadmin.plugin.substitution.dir";
+    static final int PLUGIN_RANKING = 500;
+
+    static final Logger LOG = LoggerFactory.getLogger(K8SSecretsConfigurationPlugin.class);
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        String directory = context.getProperty(DIR_PROPERTY);
+        if (directory == null) {
+            LOG.warn("Framework property '" + DIR_PROPERTY + "' not specified. Plugin disabled.");
+            return;
+        }
+
+        ConfigurationPlugin plugin = new K8SSecretsConfigurationPlugin(directory);
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put(ConfigurationPlugin.CM_RANKING, PLUGIN_RANKING);
+        props.put(DIR_PROPERTY, directory);
+        context.registerService(ConfigurationPlugin.class, plugin, props);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        // Service is automatically unregistered when bundle is stopped.
+    }
+}

Added: felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPlugin.java
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPlugin.java?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPlugin.java (added)
+++ felix/trunk/configadmin-plugins/substitution/src/main/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPlugin.java Fri Jul 26 08:21:10 2019
@@ -0,0 +1,102 @@
+/*
+ * 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.felix.configadmin.plugin.substitution;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationPlugin;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class K8SSecretsConfigurationPlugin implements ConfigurationPlugin {
+    private static final String PREFIX = "$[secret:";
+    private static final String SUFFIX = "]";
+    private static final Pattern SECRET_PATTERN =
+            Pattern.compile("\\Q" + PREFIX + "\\E.+\\Q" + SUFFIX + "\\E");
+
+    private final File directory;
+
+    K8SSecretsConfigurationPlugin(String dir) {
+        directory = new File(dir);
+        getLog().info("Configured directory for secrets: {}", dir);
+    }
+
+    private Logger getLog() {
+        return Activator.LOG;
+    }
+
+    @Override
+    public void modifyConfiguration(ServiceReference<?> reference, Dictionary<String, Object> properties) {
+        for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements(); ) {
+            String key = keys.nextElement();
+            Object val = properties.get(key);
+            if (val instanceof String) {
+                String sv = (String) val;
+                if (sv.contains(PREFIX)) {
+                    try {
+                        Object newVal = replaceVariables(sv);
+                        properties.put(key, newVal);
+                        getLog().info("Replaced value of configuration property '{}' for PID {}",
+                                key, properties.get(Constants.SERVICE_PID));
+                    } catch (IOException e) {
+                        getLog().error("Problem replacing configuration property '{}' for PID {}",
+                                key, properties.get(Constants.SERVICE_PID), e);
+                    }
+                }
+            }
+        }
+    }
+
+    Object replaceVariables(final Object value) throws IOException {
+        if (!(value instanceof String)) {
+            return value;
+        }
+
+        final String textWithVars = (String) value;
+
+        final Matcher m = SECRET_PATTERN.matcher(textWithVars.toString());
+        final StringBuffer sb = new StringBuffer();
+        while (m.find()) {
+            final String var = m.group();
+
+            final int len = var.length();
+            final String fname = var.substring(PREFIX.length(), len - SUFFIX.length());
+            if (fname.contains("..")) {
+                getLog().error("Illegal secret location: " + fname + " Going up in the directory structure is not allowed");
+                continue;
+            }
+
+            File file = new File(directory, fname);
+            if (!file.isFile()) {
+                getLog().warn("Cannot replace variable. Configured path is not a regular file: " + file);
+                continue;
+            }
+            String replacement = new String(Files.readAllBytes(file.toPath())).trim();
+            m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
+        }
+        m.appendTail(sb);
+
+        return sb.toString();
+    }
+}

Added: felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/ActivatorTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/ActivatorTest.java?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/ActivatorTest.java (added)
+++ felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/ActivatorTest.java Fri Jul 26 08:21:10 2019
@@ -0,0 +1,76 @@
+/*
+ * 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.felix.configadmin.plugin.substitution;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationPlugin;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import static org.junit.Assert.assertEquals;
+
+public class ActivatorTest {
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testStart() throws Exception {
+        final Dictionary<String, Object> regProps = new Hashtable<>();
+
+        BundleContext ctx = Mockito.mock(BundleContext.class);
+        Mockito.when(ctx.getProperty(Activator.DIR_PROPERTY)).thenReturn("/tmp/somewhere");
+        Mockito.when(ctx.registerService(
+            Mockito.eq(ConfigurationPlugin.class),
+            Mockito.isA(ConfigurationPlugin.class),
+            Mockito.isA(Dictionary.class))).then(
+                new Answer<ServiceRegistration<?>>() {
+                    @Override
+                    public ServiceRegistration<?> answer(InvocationOnMock invocation) throws Throwable {
+                        Dictionary<String, Object> props = (Dictionary<String, Object>) invocation.getArguments()[2];
+                        for (Enumeration<String> e = props.keys(); e.hasMoreElements(); ) {
+                            String key = e.nextElement();
+                            regProps.put(key, props.get(key));
+                        }
+                        return null;
+                    }
+                });
+        Activator a = new Activator();
+        a.start(ctx);
+
+        Dictionary<String, Object> expected = new Hashtable<>();
+        expected.put(Activator.DIR_PROPERTY, "/tmp/somewhere");
+        expected.put(ConfigurationPlugin.CM_RANKING, Activator.PLUGIN_RANKING);
+        assertEquals(expected, regProps);
+    }
+
+    @Test
+    public void testMissingConfiguration() throws Exception {
+        BundleContext bc = Mockito.mock(BundleContext.class);
+
+        Activator a = new Activator();
+        a.start(bc);
+        a.stop(bc);
+
+        Mockito.verify(bc).getProperty(Activator.DIR_PROPERTY);
+        Mockito.verifyNoMoreInteractions(bc);
+    }
+}

Added: felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPluginTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPluginTest.java?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPluginTest.java (added)
+++ felix/trunk/configadmin-plugins/substitution/src/test/java/org/apache/felix/configadmin/plugin/substitution/K8SSecretsConfigurationPluginTest.java Fri Jul 26 08:21:10 2019
@@ -0,0 +1,92 @@
+/*
+ * 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.felix.configadmin.plugin.substitution;
+
+import org.junit.Test;
+import org.osgi.framework.Constants;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class K8SSecretsConfigurationPluginTest {
+    @Test
+    public void testModifyConfiguration() throws Exception {
+        String rf = getClass().getResource("/testfile").getFile();
+
+        K8SSecretsConfigurationPlugin plugin = new K8SSecretsConfigurationPlugin(
+                new File(rf).getParent());
+
+        Dictionary<String, Object> dict = new Hashtable<>();
+        dict.put("foo", "bar");
+        dict.put("replaced", "$[secret:testfile]");
+        dict.put("intval", 999);
+        dict.put(Constants.SERVICE_PID, "my.service");
+        plugin.modifyConfiguration(null, dict);
+
+        assertEquals(4, dict.size());
+        assertEquals("bar", dict.get("foo"));
+        assertEquals("line1\nline2", dict.get("replaced"));
+        assertEquals("my.service", dict.get(Constants.SERVICE_PID));
+        assertEquals(999, dict.get("intval"));
+    }
+
+    @Test
+    public void testSubdirReplacement() throws Exception {
+        String rf = getClass().getResource("/sub/sub2/testfile2").getFile();
+
+        K8SSecretsConfigurationPlugin plugin = new K8SSecretsConfigurationPlugin(
+                new File(rf).getParentFile().getParent());
+
+        Dictionary<String, Object> dict = new Hashtable<>();
+        dict.put("substed", "$[secret:sub2/testfile2]");
+        dict.put("not1", "$[secret:../testfile]");
+        dict.put("not2", "$[secret:sub2/../../testfile.txt]");
+        plugin.modifyConfiguration(null, dict);
+
+        assertEquals(3, dict.size());
+        assertEquals("the_content", dict.get("substed"));
+        assertEquals("$[secret:../testfile]", dict.get("not1"));
+        assertEquals("$[secret:sub2/../../testfile.txt]", dict.get("not2"));
+    }
+
+    @Test
+    public void testReplacement() throws Exception {
+        String rf = getClass().getResource("/testfile.txt").getFile();
+        K8SSecretsConfigurationPlugin plugin = new K8SSecretsConfigurationPlugin(
+                new File(rf).getParent());
+
+        assertEquals("xxla la layy", plugin.replaceVariables("xx$[secret:testfile.txt]yy"));
+        String doesNotReplace = "xx$[" + rf + "]yy";
+        assertEquals(doesNotReplace, plugin.replaceVariables(doesNotReplace));
+    }
+
+    @Test
+    public void testNoReplacement() throws IOException {
+        String rf = getClass().getResource("/testfile.txt").getFile();
+        K8SSecretsConfigurationPlugin plugin = new K8SSecretsConfigurationPlugin(
+                new File(rf).getParent());
+
+        assertEquals("foo", plugin.replaceVariables("foo"));
+        assertNull(plugin.replaceVariables(null));
+        assertEquals(123L, plugin.replaceVariables(123L));
+    }
+}

Added: felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/testfile2
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/testfile2?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/testfile2 (added)
+++ felix/trunk/configadmin-plugins/substitution/src/test/resources/sub/sub2/testfile2 Fri Jul 26 08:21:10 2019
@@ -0,0 +1 @@
+the_content
\ No newline at end of file

Added: felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile (added)
+++ felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile Fri Jul 26 08:21:10 2019
@@ -0,0 +1,2 @@
+line1
+line2
\ No newline at end of file

Added: felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile.txt
URL: http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile.txt?rev=1863778&view=auto
==============================================================================
--- felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile.txt (added)
+++ felix/trunk/configadmin-plugins/substitution/src/test/resources/testfile.txt Fri Jul 26 08:21:10 2019
@@ -0,0 +1 @@
+la la la
\ No newline at end of file