GERONIMO-6699 GERONIMO-6700 microprofile config docker and secured converter extensions
diff --git a/geronimo-microprofile-aggregator/pom.xml b/geronimo-microprofile-aggregator/pom.xml
index 082d007..a678543 100644
--- a/geronimo-microprofile-aggregator/pom.xml
+++ b/geronimo-microprofile-aggregator/pom.xml
@@ -38,7 +38,7 @@
     <dependency>
       <groupId>org.eclipse.microprofile.config</groupId>
       <artifactId>microprofile-config-api</artifactId>
-      <version>1.3</version>
+      <version>${microprofile-config-api.version}</version>
       <exclusions>
         <exclusion>
           <groupId>org.osgi</groupId>
@@ -49,7 +49,7 @@
     <dependency>
       <groupId>org.apache.geronimo.config</groupId>
       <artifactId>geronimo-config-impl</artifactId>
-      <version>1.2.1</version>
+      <version>${geronimo-config.version}</version>
     </dependency>
 
     <!-- fault-tolerance -->
diff --git a/geronimo-microprofile-site/src/main/jbake/content/extensions.adoc b/geronimo-microprofile-site/src/main/jbake/content/extensions.adoc
new file mode 100644
index 0000000..25ae0f0
--- /dev/null
+++ b/geronimo-microprofile-site/src/main/jbake/content/extensions.adoc
@@ -0,0 +1,92 @@
+= Apache Geronimo Microprofile Extensions
+:jbake-date: 2018-07-24
+:icons: font
+
+Apache Geronimo Extension is a set of module integration with Microprofile and enriching it
+transparently with production/cloud ready features.
+
+== Secured credentials
+
+Instead of ensuring all the `ConfigSource` you use through Microprofile Config are secured
+you can plug our `secured-string-converter` and all string values will get support for a secured storage.
+
+=== Dependency
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.apache.geronimo</groupId>
+  <artifactId>secured-string-converter</artifactId>
+  <version>${geronimo-microprofile.version}</version>
+</dependency>
+----
+
+=== Usage
+
+A security mechanism allows to use a ciphered value instead of a clear value for passing sensitive data (password, secret, token, etc.).
+
+It relies on `org.apache.geronimo.microprofile.extensions.config.converter.secure.ConfigurationMain`, which allows you to:
+
+1. create a `master_key` file
+2. encrypt a value
+3. decrypt a value (for testing purposes)
+
+A master password is stored and obfuscated in a `master_key` file. The algorithm `AES/CBC/PKCS5Padding` ciphers the value and encodes the result in base64. This mechanism allows to manage the value as plain text and easily pass it through all potential ways you can set the configuration.
+
+Once value ciphered with the master key you can reference their ciphered value prefixed by `secure:` in your configuration
+and the library will decipher them before passing the value to your application.
+
+The command examples in the sections below assume you have set the alias in your profile:
+
+[source,sh]
+----
+alias gssc="java -cp secured-string-converter.jar org.apache.geronimo.microprofile.extensions.config.converter.secure.ConfigurationMain"
+----
+
+If it is not the case, replace `gssc` by the full alias value.
+
+==== Generating the master key
+
+To generate the master key, use the provided main:
+
+[source,sh]
+----
+gssc --master-key /path/to/my_master_key [secret-value]
+----
+
+IMPORTANT: Set the `geronimo.microprofile.extensions.config.converter.secure.master_key.location` system property on the server to ensure it uses this master key. Make sure that only the applications needing this key can read it.
+
+==== Encrypting a value
+
+To encrypt a value, use the provided main:
+
+[source,sh]
+----
+gssc --encrypt /path/to/my_master_key my_credential_to_encrypt
+----
+
+==== Decrypting a value
+
+To decrypt a value, use the provided main:
+
+[source,sh]
+----
+gssc --decrypt /path/to/my_master_key secure:my_credential_to_encrypt
+----
+
+== Docker configs/secrets integration
+
+=== Docker Config
+
+org.apache.geronimo.microprofile.extensions.config.docker.DockerConfigConfigSource.ordinal:: config source ordinal. Defaults to `100`.
+org.apache.geronimo.microprofile.extensions.config.docker.DockerConfigConfigSource.base:: configuration location. Defaults to `/`.
+org.apache.geronimo.microprofile.extensions.config.docker.DockerConfigConfigSource.prefixes:: file name prefixes to take into account. Defaults to nothing, which means that all prefixes but the common UNIx exclusions are taken into account.
+
+For example, creating a `/app.foo.bar` file makes its content available under the `app.foo.bar` key.
+
+=== Docker Secrets
+
+org.apache.geronimo.microprofile.extensions.config.docker.DockerSecretConfigSource.ordinal:: config source ordinal. Defaults to `100`.
+org.apache.geronimo.microprofile.extensions.config.docker.DockerSecretConfigSource.base:: secrets location. Defaults to `/run/secrets`.
+
+For example, creating a `/run/secrets/app.foo.bar` file makes its content available under the `app.foo.bar` key.
diff --git a/geronimo-microprofile-site/src/main/jbake/content/index.adoc b/geronimo-microprofile-site/src/main/jbake/content/index.adoc
index 6d1f1f3..77eb3dd 100644
--- a/geronimo-microprofile-site/src/main/jbake/content/index.adoc
+++ b/geronimo-microprofile-site/src/main/jbake/content/index.adoc
@@ -31,7 +31,7 @@
 
 If you want to investigate your Microprofile setup you can also play with our reporter link:reporter.html[UI/webapp].
 
-Finally we provide μ~ (utilda) which is a pom with link:http://openwebbeans.apache.org/meecrowave/[Apache Meecrowave] and the Aggregator pom which makes
+We also provide μ~ (utilda) which is a pom with link:http://openwebbeans.apache.org/meecrowave/[Apache Meecrowave] and the Aggregator pom which makes
 you a one dependency Microprofile 2.0 server, ready to use:
 
 [source,xml]
@@ -45,3 +45,6 @@
 ----
 
 WARNING: the 1.0.0 release binary has some versioning issues, ensure to evaluate the snapshot until we release next version please.
+
+Finally we provide a set of Microprofile extensions to make it faster to be production and cloud ready
+like a secure Microprofile config converter to credentials and a Docker integration. Read more on the dedicated link:extensions.html[page].
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/pom.xml b/microprofile-extensions/microprofile-extensions-config/docker-configsource/pom.xml
new file mode 100644
index 0000000..0deaa93
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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>
+    <artifactId>microprofile-extensions-config</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>docker-configsource</artifactId>
+  <name>Geronimo Microprofile :: Extensions :: Config :: Docker</name>
+
+  <properties>
+    <geronimo-microprofile.Automatic-Module-Name>${project.groupId}.microprofile.extensions.config.docker</geronimo-microprofile.Automatic-Module-Name>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.microprofile.config</groupId>
+      <artifactId>microprofile-config-api</artifactId>
+      <version>${microprofile-config-api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.geronimo.config</groupId>
+      <artifactId>geronimo-config-impl</artifactId>
+      <version>${geronimo-config.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/BaseConfigSource.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/BaseConfigSource.java
new file mode 100644
index 0000000..6b2c277
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/BaseConfigSource.java
@@ -0,0 +1,110 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.docker;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.time.Clock;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+abstract class BaseConfigSource implements ConfigSource {
+
+    private final Supplier<Map<String, String>> loader;
+
+    private final int ordinal;
+
+    private final Clock clock = Clock.systemUTC();
+
+    private final long updateInterval;
+
+    private final Map<String, String> mapping;
+
+    private volatile Map<String, String> entries;
+
+    private volatile long lastUpdate = -1;
+
+    BaseConfigSource(final Supplier<Map<String, String>> loader, final int ordinal) {
+        this.loader = loader;
+        this.ordinal = ordinal;
+        this.updateInterval = Long.parseLong(InternalConfig.get(getClass().getName() + ".updateInterval", "10000"));
+        this.mapping = asMap(InternalConfig.get(getClass().getName() + ".keyMapping", ""));
+        doLoad();
+    }
+
+    @Override
+    public int getOrdinal() {
+        return ordinal;
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        reloadIfNeeded();
+        return new HashMap<>(entries);
+    }
+
+    @Override
+    public String getValue(final String propertyName) {
+        reloadIfNeeded();
+        return entries.get(propertyName);
+    }
+
+    private Map<String, String> asMap(final String props) {
+        final Properties properties = new Properties();
+        try (final StringReader reader = new StringReader(props)) {
+            properties.load(reader);
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+        return properties.stringPropertyNames().stream().collect(toMap(identity(), properties::getProperty));
+    }
+
+    private void doLoad() {
+        lastUpdate = clock.millis(); // first to avoid concurrent updates
+        final Map<String, String> entries = loader.get();
+        if (mapping.isEmpty()) {
+            this.entries = entries;
+        } else {
+            this.entries = entries
+                    .entrySet()
+                    .stream()
+                    .collect(toMap(it -> mapping.getOrDefault(it.getKey(), it.getKey()), Map.Entry::getValue));
+        }
+    }
+
+    private void reloadIfNeeded() {
+        if (clock.millis() - lastUpdate > updateInterval) {
+            final long start = clock.millis();
+            doLoad();
+            final long end = clock.millis();
+            final long duration = end - start;
+            if (duration > updateInterval) {
+                Logger
+                        .getLogger(getClass().getName())
+                        .warning(() -> "Reloading the configuration took more than expected: " + duration + "ms");
+            }
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSource.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSource.java
new file mode 100644
index 0000000..0c815cd
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSource.java
@@ -0,0 +1,89 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.docker;
+
+import static java.util.Collections.emptyMap;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+public class DockerConfigConfigSource extends BaseConfigSource implements ConfigSource {
+
+    public DockerConfigConfigSource() {
+        this(InternalConfig.get(DockerConfigConfigSource.class.getName() + ".base", "/"),
+                Integer.parseInt(InternalConfig.get(DockerConfigConfigSource.class.getName() + ".ordinal", "100")),
+                Stream
+                        .of(InternalConfig.get(DockerConfigConfigSource.class.getName() + ".prefixes", "").split(","))
+                        .map(String::trim)
+                        .filter(it -> !it.isEmpty())
+                        .toArray(String[]::new));
+    }
+
+    public DockerConfigConfigSource(final String base, final int ordinal, final String... prefixes) {
+        super(() -> reload(base, prefixes), ordinal);
+    }
+
+    @Override
+    public String getName() {
+        return "docker-configs";
+    }
+
+    private static Map<String, String> reload(final String base, final String... prefixes) {
+        final Path from = Paths.get(base);
+        if (!Files.exists(from)) {
+            return emptyMap();
+        }
+        final Predicate<Path> matches =
+                // if no prefix ensure it is not default unix folders or not supported config files
+                prefixes.length == 0
+                        ? path -> !Files.isDirectory(path) && Stream
+                                .of(".xml", ".properties", ".yml", ".yaml", ".so", ".json", ".old", ".img", "vmlinuz",
+                                        "core")
+                                .noneMatch(ext -> path.getFileName().toString().endsWith(ext))
+                        : path -> Stream
+                                .of(prefixes)
+                                .anyMatch(prefix -> path.getFileName().toString().startsWith(prefix));
+        try {
+            return Files
+                    .list(from)
+                    .filter(matches)
+                    .map(path -> new AbstractMap.SimpleEntry<>(path.getFileName().toString(), read(path)))
+                    .filter(e -> e.getValue() != null)
+                    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private static String read(final Path path) {
+        try {
+            return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+        } catch (final Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSource.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSource.java
new file mode 100644
index 0000000..73ec865
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSource.java
@@ -0,0 +1,64 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.docker;
+
+import static java.util.Collections.emptyMap;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+public class DockerSecretConfigSource extends BaseConfigSource implements ConfigSource {
+
+    public DockerSecretConfigSource() {
+        this(InternalConfig.get(DockerSecretConfigSource.class.getName() + ".base", "/run/secrets"),
+                Integer.parseInt(InternalConfig.get(DockerSecretConfigSource.class.getName() + ".ordinal", "100")));
+    }
+
+    public DockerSecretConfigSource(final String base, final int ordinal) {
+        super(() -> reload(base), ordinal);
+    }
+
+    @Override
+    public String getName() {
+        return "docker-secrets";
+    }
+
+    private static Map<String, String> reload(final String base) {
+        final Path from = Paths.get(base);
+        if (!Files.exists(from)) {
+            return emptyMap();
+        }
+        try {
+            return Files.list(from).collect(toMap(it -> it.getFileName().toString(), it -> {
+                try {
+                    return new String(Files.readAllBytes(it), StandardCharsets.UTF_8);
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+            }));
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/InternalConfig.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/InternalConfig.java
new file mode 100644
index 0000000..e7306a5
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/java/org/apache/geronimo/microprofile/extensions/config/docker/InternalConfig.java
@@ -0,0 +1,73 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.docker;
+
+import static java.util.Optional.ofNullable;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+
+// just a way to have internal config for the sources in the app and not only system props
+// we can't use Config here so back to the plain old singleton pattern
+final class InternalConfig {
+
+    private static final InternalConfig CONFIG = new InternalConfig();
+
+    private final Map<String, String> config;
+
+    private InternalConfig() {
+        final Thread thread = Thread.currentThread();
+        final ClassLoader old = thread.getContextClassLoader();
+        thread.setContextClassLoader(InternalConfig.class.getClassLoader());
+        try {
+            final Properties properties = loadInternalProperties();
+            config = properties.stringPropertyNames().stream().collect(toMap(identity(), properties::getProperty));
+        } finally {
+            thread.setContextClassLoader(old);
+        }
+    }
+
+    static String get(final String key, final String defaultValue) {
+        return ofNullable(CONFIG.config.get(key)).orElseGet(() -> System.getProperty(key, defaultValue));
+    }
+
+    private Properties loadInternalProperties() {
+        final Properties properties = new Properties();
+        final ClassLoader loader =
+                ofNullable(Thread.currentThread().getContextClassLoader()).orElseGet(ClassLoader::getSystemClassLoader);
+        final InputStream stream = loader.getResourceAsStream(
+                "META-INF/geronimo/microprofile/extensions/config/docker/configuration.properties");
+        if (stream != null) {
+            try {
+                properties.load(stream);
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            } finally {
+                try {
+                    stream.close();
+                } catch (final IOException e) {
+                    // no-op
+                }
+            }
+        }
+        return properties;
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
new file mode 100644
index 0000000..f444d80
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
@@ -0,0 +1,2 @@
+org.apache.geronimo.microprofile.extensions.config.docker.DockerSecretConfigSource
+org.apache.geronimo.microprofile.extensions.config.docker.DockerConfigConfigSource
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSourceTest.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSourceTest.java
new file mode 100644
index 0000000..d158865
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerConfigConfigSourceTest.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.geronimo.microprofile.extensions.config.docker;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.stream.StreamSupport;
+
+import org.apache.geronimo.microprofile.extensions.config.docker.DockerConfigConfigSource;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class DockerConfigConfigSourceTest {
+
+    @BeforeEach
+    @AfterEach
+    void clear() {
+        ConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig());
+    }
+
+    @Test
+    void testConfigs(@TempDir final Path base) throws IOException {
+        Files.createDirectories(base);
+        System.setProperty(DockerConfigConfigSource.class.getName() + ".base", base.toAbsolutePath().toString());
+        System.setProperty(DockerConfigConfigSource.class.getName() + ".prefixes", "my.");
+        try {
+            Files
+                    .write(base.resolve("my.config.1"), "My first config".getBytes(StandardCharsets.UTF_8),
+                            StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
+            Files
+                    .write(base.resolve("my.config.2"), "My second config".getBytes(StandardCharsets.UTF_8),
+                            StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
+            final Config config = ConfigProvider.getConfig();
+            assertEquals("My first config", config.getValue("my.config.1", String.class));
+            assertEquals("My second config", config.getValue("my.config.2", String.class));
+            assertEquals(2,
+                    StreamSupport
+                            .stream(config.getConfigSources().spliterator(), false)
+                            .filter(DockerConfigConfigSource.class::isInstance)
+                            .findFirst()
+                            .orElseThrow(IllegalStateException::new)
+                            .getPropertyNames()
+                            .size());
+        } finally {
+            System.clearProperty("geronimo.microprofile.extensions.config.docker.configs.base");
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSourceTest.java b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSourceTest.java
new file mode 100644
index 0000000..d083d80
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/docker-configsource/src/test/java/org/apache/geronimo/microprofile/extensions/config/docker/DockerSecretConfigSourceTest.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.geronimo.microprofile.extensions.config.docker;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.stream.StreamSupport;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class DockerSecretConfigSourceTest {
+
+    @BeforeEach
+    @AfterEach
+    void clear() {
+        ConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig());
+    }
+
+    @Test
+    void testSecrets(@TempDir final Path base) throws IOException {
+        Files.createDirectories(base);
+        System
+                .setProperty("org.apache.geronimo.microprofile.extensions.config.docker.DockerSecretConfigSource.base",
+                        base.toAbsolutePath().toString());
+        try {
+            Files
+                    .write(base.resolve("my.secret.1"), "My first secret".getBytes(StandardCharsets.UTF_8),
+                            StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
+            Files
+                    .write(base.resolve("my.secret.2"), "My second secret".getBytes(StandardCharsets.UTF_8),
+                            StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
+            final Config config = ConfigProvider.getConfig();
+            assertEquals("My first secret", config.getValue("my.secret.1", String.class));
+            assertEquals("My second secret", config.getValue("my.secret.2", String.class));
+            assertEquals(2,
+                    StreamSupport
+                            .stream(config.getConfigSources().spliterator(), false)
+                            .filter(DockerSecretConfigSource.class::isInstance)
+                            .findFirst()
+                            .orElseThrow(IllegalStateException::new)
+                            .getPropertyNames()
+                            .size());
+        } finally {
+            System.clearProperty("geronimo.microprofile.extensions.config.docker.secrets.base");
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/pom.xml b/microprofile-extensions/microprofile-extensions-config/pom.xml
new file mode 100644
index 0000000..6c3b081
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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">
+  <parent>
+    <artifactId>microprofile-extensions</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.2-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>microprofile-extensions-config</artifactId>
+  <name>Geronimo Microprofile :: Extensions :: Config</name>
+  <packaging>pom</packaging>
+  <description>A parent pom for microprofile config extensions.</description>
+
+  <modules>
+    <module>docker-configsource</module>
+    <module>secured-string-converter</module>
+  </modules>
+</project>
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/pom.xml b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/pom.xml
new file mode 100644
index 0000000..5f3b9d2
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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>
+    <artifactId>microprofile-extensions-config</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.2-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>secured-string-converter</artifactId>
+  <name>Geronimo Microprofile :: Extensions :: Config :: Secured Converter</name>
+
+  <properties>
+    <geronimo-microprofile.Automatic-Module-Name>${project.groupId}.microprofile.extensions.config.secured</geronimo-microprofile.Automatic-Module-Name>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.microprofile.config</groupId>
+      <artifactId>microprofile-config-api</artifactId>
+      <version>${microprofile-config-api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jcdi_2.0_spec</artifactId>
+      <version>1.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-annotation_1.3_spec</artifactId>
+      <version>1.1</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.geronimo.config</groupId>
+      <artifactId>geronimo-config-impl</artifactId>
+      <version>${geronimo-config.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverter.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverter.java
new file mode 100644
index 0000000..8b6da32
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverter.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.geronimo.microprofile.extensions.config.converter.secure;
+
+import static java.util.Optional.ofNullable;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.Vetoed;
+
+import org.eclipse.microprofile.config.spi.Converter;
+
+@Vetoed // loaded by SPI - almost the exact same impl than maven, see org.sonatype.plexus.components.cipher.PBECipher
+@Priority(100)
+public class CipheredStringConverter implements Converter<String> {
+
+    private static final String SECURE_PREFIX = "secure:";
+
+    private final byte[] masterPassword;
+
+    public CipheredStringConverter() {
+        this(readMasterPassword());
+    }
+
+    protected CipheredStringConverter(final byte[] pass) {
+        masterPassword = pass;
+    }
+
+    @Override
+    public String convert(final String value) {
+        if (value == null || !value.startsWith(SECURE_PREFIX) || !isActive()) {
+            return value;
+        }
+        return new PBECipher().decrypt64(value.substring(SECURE_PREFIX.length()), masterPassword);
+    }
+
+    public String cipher(final String value) {
+        try {
+            return SECURE_PREFIX + new PBECipher().encrypt64(value, masterPassword);
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private boolean isActive() {
+        return masterPassword != null;
+    }
+
+    private static byte[] readMasterPassword() {
+        return ofNullable(System
+                .getProperty("geronimo.microprofile.extensions.config.converter.secure.master_key.location",
+                        new File(System.getProperty("meecrowave.base", System.getProperty("catalina.base", "")), "conf/master_key").getAbsolutePath()))
+                                .map(path -> Paths.get(path))
+                                .filter(Files::exists)
+                                .map(it -> MasterKey.read(it.toAbsolutePath().toString()))
+                                .orElse(null);
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/ConfigurationMain.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/ConfigurationMain.java
new file mode 100644
index 0000000..4111c8a
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/ConfigurationMain.java
@@ -0,0 +1,63 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.converter.secure;
+
+import java.util.UUID;
+import java.util.stream.IntStream;
+
+public class ConfigurationMain {
+    private ConfigurationMain() {
+        // no-op
+    }
+
+    public static void main(final String[] args) {
+        if (args.length == 0) {
+            usage();
+        }
+
+        switch (args[0].trim()) {
+        case "--encrypt":
+            ensureArgs(args, 3);
+            System.out.println("Value: 'secure:" + new PBECipher().encrypt64(args[2], MasterKey.read(args[1])) + "'");
+            break;
+        case "--decrypt":
+            ensureArgs(args, 3);
+            System.out.println("Value: '" + new PBECipher().decrypt64(args[2], MasterKey.read(args[1])) + "'");
+            break;
+        case "--master-key":
+            ensureArgs(args, 2, 3);
+            MasterKey.write(args[1], args.length == 2 ? UUID.randomUUID().toString() : args[2]);
+            System.out.println("Generated '" + args[1] + "'");
+            break;
+        default:
+            usage();
+        }
+    }
+
+    private static void ensureArgs(final String[] args, final int... len) {
+        if (IntStream.of(len).noneMatch(v -> args.length == v)) {
+            usage();
+        }
+    }
+
+    private static void usage() {
+        throw new IllegalArgumentException("Usage:\n" + "  java -cp secured-string-converter.jar "
+                + ConfigurationMain.class.getName() + " \n"
+                + " --encrypt master_key_path value_to_encrypt\n" + " --decrypt master_key_path value_to_decrypt\n"
+                + " --master-key master_key_path key_value_or_generate_an_uuid\n");
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKey.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKey.java
new file mode 100644
index 0000000..305646e
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKey.java
@@ -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.geronimo.microprofile.extensions.config.converter.secure;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.stream.IntStream;
+
+class MasterKey {
+
+    private static final int SIZE =
+            Integer.getInteger("geronimo.microprofile.extensions.config.converter.secure.master_key.size", 2 * 1024 * 1024);
+
+    private MasterKey() {
+        // no-op
+    }
+
+    static void write(final String location, final String masterKey) {
+        final byte[] key = masterKey.getBytes(StandardCharsets.UTF_8);
+
+        try (final DataOutputStream stream =
+                new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(Paths.get(location))))) {
+            final int[] indices = new int[key.length];
+            final byte[] data = new byte[SIZE];
+
+            final SecureRandom secureRandom = new SecureRandom();
+            new Random(System.currentTimeMillis()).nextBytes(data);
+            for (int i = 0; i < key.length; i++) {
+                final int index = secureRandom.nextInt(SIZE);
+                indices[i] = index;
+                data[index] = key[i];
+            }
+            stream.writeInt(key.length);
+            for (final int keyByte : indices) {
+                stream.writeInt(keyByte);
+            }
+            stream.write(data);
+            final byte[] footer = new byte[secureRandom.nextInt(key.length)];
+            secureRandom.nextBytes(footer);
+            stream.write(footer);
+        } catch (final IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    static byte[] read(final String location) {
+        try (final DataInputStream in =
+                new DataInputStream(new BufferedInputStream(Files.newInputStream(Paths.get(location))))) {
+            final int len = in.readInt();
+            final byte[] masterKey = new byte[len];
+            final int[] keyIndices = new int[len];
+            for (int i = 0; i < len; i++) {
+                keyIndices[i] = in.readInt();
+            }
+
+            final int maxIdx = IntStream.of(keyIndices).max().orElse(0) + 1;
+            final byte[] data = new byte[maxIdx];
+            final int nbRead = in.read(data);
+            if (nbRead != maxIdx) {
+                throw new IllegalStateException("Corrupted master_key: '" + location + "'");
+            }
+            for (int i = 0; i < masterKey.length; i++) {
+                masterKey[i] = data[keyIndices[i]];
+            }
+            return masterKey;
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/PBECipher.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/PBECipher.java
new file mode 100644
index 0000000..c4ccabd
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/PBECipher.java
@@ -0,0 +1,167 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.converter.secure;
+
+import static java.util.Locale.ROOT;
+
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+// from plexus-cipher - reformatted for this project rules
+class PBECipher {
+
+    private static final int SPICE_SIZE = 16;
+
+    private static final int SALT_SIZE = 8;
+
+    private static final int CHUNK_SIZE = 16;
+
+    private static final String DIGEST_ALG = "SHA-256";
+
+    private static final String KEY_ALG = "AES";
+
+    private static final String CIPHER_ALG = "AES/CBC/PKCS5Padding";
+
+    private final MessageDigest digester;
+
+    private final SecureRandom secureRandom;
+
+    PBECipher() {
+        try {
+            digester = MessageDigest.getInstance(DIGEST_ALG);
+            final boolean onLinux = System.getProperty("os.name", "blah").toLowerCase(ROOT).contains("linux");
+            if (!onLinux) {
+                secureRandom = new SecureRandom();
+            } else {
+                secureRandom = null;
+            }
+
+        } catch (final NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private byte[] getSalt(final int numBytes) {
+        if (secureRandom != null) {
+            secureRandom.setSeed(System.currentTimeMillis());
+            return secureRandom.generateSeed(numBytes);
+        }
+        final byte[] res = new byte[numBytes];
+        new Random(System.currentTimeMillis()).nextBytes(res);
+        return res;
+    }
+
+    String encrypt64(final String clearText, final byte[] password) {
+        try {
+            final byte[] clearBytes = clearText.getBytes(StandardCharsets.UTF_8);
+            final byte[] salt = getSalt(SALT_SIZE);
+            if (secureRandom != null) {
+                new SecureRandom().nextBytes(salt);
+            }
+
+            final Cipher cipher = createCipher(password, salt, Cipher.ENCRYPT_MODE);
+            final byte[] encryptedBytes = cipher.doFinal(clearBytes);
+            final int len = encryptedBytes.length;
+            final byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE);
+            final int totalLen = SALT_SIZE + len + padLen + 1;
+            final byte[] allEncryptedBytes = getSalt(totalLen);
+            System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE);
+            allEncryptedBytes[SALT_SIZE] = padLen;
+            System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len);
+
+            final byte[] encryptedTextBytes = Base64.getEncoder().encode(allEncryptedBytes);
+            return new String(encryptedTextBytes, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    String decrypt64(final String encryptedText, final byte[] password) {
+        try {
+            final byte[] allEncryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
+            final int totalLen = allEncryptedBytes.length;
+            final byte[] salt = new byte[SALT_SIZE];
+            System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE);
+            final byte padLen = allEncryptedBytes[SALT_SIZE];
+            final byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen];
+            System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length);
+            final Cipher cipher = createCipher(password, salt, Cipher.DECRYPT_MODE);
+            final byte[] clearBytes = cipher.doFinal(encryptedBytes);
+            return new String(clearBytes, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private Cipher createCipher(final byte[] pwdAsBytes, final byte[] inputSalt, final int mode)
+            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        digester.reset();
+
+        final byte[] keyAndIv = new byte[SPICE_SIZE * 2];
+        final byte[] salt;
+        if (inputSalt == null || inputSalt.length == 0) {
+            salt = null; // no salt, likely bad
+        } else {
+            salt = inputSalt;
+        }
+
+        byte[] result;
+        int currentPos = 0;
+        while (currentPos < keyAndIv.length) {
+            digester.update(pwdAsBytes);
+
+            if (salt != null) {
+                digester.update(salt, 0, 8);
+            }
+            result = digester.digest();
+
+            final int stillNeed = keyAndIv.length - currentPos;
+            if (result.length > stillNeed) {
+                final byte[] b = new byte[stillNeed];
+                System.arraycopy(result, 0, b, 0, b.length);
+                result = b;
+            }
+
+            System.arraycopy(result, 0, keyAndIv, currentPos, result.length);
+            currentPos += result.length;
+            if (currentPos < keyAndIv.length) {
+                digester.reset();
+                digester.update(result);
+            }
+        }
+
+        final byte[] key = new byte[SPICE_SIZE];
+        final byte[] iv = new byte[SPICE_SIZE];
+        System.arraycopy(keyAndIv, 0, key, 0, key.length);
+        System.arraycopy(keyAndIv, key.length, iv, 0, iv.length);
+        final Cipher cipher = Cipher.getInstance(CIPHER_ALG);
+        cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv));
+        return cipher;
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter
new file mode 100644
index 0000000..b5f6db3
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter
@@ -0,0 +1 @@
+org.apache.geronimo.microprofile.extensions.config.converter.secure.CipheredStringConverter
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverterTest.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverterTest.java
new file mode 100644
index 0000000..02faaae
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/CipheredStringConverterTest.java
@@ -0,0 +1,93 @@
+/**
+ * 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.geronimo.microprofile.extensions.config.converter.secure;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class CipheredStringConverterTest {
+
+    @ParameterizedTest
+    @ValueSource(strings = { "simple", "v@lue", "numb3r", "153654", "dmorjezf(t_\"yà(_\"àè" })
+    void roundTrip(final String value) {
+        final byte[] secret = "test-secret".getBytes(StandardCharsets.UTF_8);
+        final CipheredStringConverter converter = new CipheredStringConverter(secret);
+        final String cipher = new PBECipher().encrypt64(value, secret);
+        assertNotEquals(value, cipher);
+        assertEquals(value, converter.convert("secure:" + cipher));
+    }
+
+    @Test
+    void configCiphered(final TestInfo testInfo, @TempDir final Path masterKeyDir) throws IOException {
+        Files.createDirectories(masterKeyDir.getParent());
+        final Path masterKey = masterKeyDir.resolve("masterKey").toAbsolutePath();
+        MasterKey.write(masterKey.toString(), "test-master-key");
+        final String oldKey = System.getProperty("geronimo.microprofile.extensions.config.converter.secure.master_key.location");
+        System.setProperty("geronimo.microprofile.extensions.config.converter.secure.master_key.location", masterKey.toString());
+        final ConfigProviderResolver resolver = ConfigProviderResolver.instance();
+        final ClassLoader loader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
+        };
+        final String propertyName = testInfo.getTestClass().orElseThrow(IllegalArgumentException::new).getName() + '.'
+                + testInfo.getTestMethod().orElseThrow(IllegalArgumentException::new).getName();
+        final Config config =
+                resolver.getBuilder().forClassLoader(loader).addDiscoveredConverters().withSources(new ConfigSource() {
+
+                    @Override
+                    public Map<String, String> getProperties() {
+                        return singletonMap(propertyName, "secure:s7umRoarnqMHLza1fgY3L7eGz1A9saTijM4htHTy1x0=");
+                    }
+
+                    @Override
+                    public String getValue(final String propertyName) {
+                        return getProperties().get(propertyName);
+                    }
+
+                    @Override
+                    public String getName() {
+                        return "test";
+                    }
+                }).build();
+        try {
+            final String value =
+                    config.getOptionalValue(propertyName, String.class).orElseThrow(IllegalArgumentException::new);
+            assertEquals("a11 cle@r man!", value);
+        } finally {
+            resolver.releaseConfig(config);
+            if (oldKey != null) {
+                System.setProperty("geronimo.microprofile.extensions.config.converter.secure.master_key.location", oldKey);
+            } else {
+                System.clearProperty("geronimo.microprofile.extensions.config.converter.secure.master_key.location");
+            }
+        }
+    }
+}
diff --git a/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKeyTest.java b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKeyTest.java
new file mode 100644
index 0000000..23ce129
--- /dev/null
+++ b/microprofile-extensions/microprofile-extensions-config/secured-string-converter/src/test/java/org/apache/geronimo/microprofile/extensions/config/converter/secure/MasterKeyTest.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.geronimo.microprofile.extensions.config.converter.secure;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class MasterKeyTest {
+
+    @Test
+    void generateAndReadKey(@TempDir final Path temp) {
+        final String keyLocation = temp.resolve("master_key").toString();
+        MasterKey.write(keyLocation, "secret");
+        assertEquals("secret", new String(MasterKey.read(keyLocation), StandardCharsets.UTF_8));
+    }
+}
diff --git a/microprofile-extensions/pom.xml b/microprofile-extensions/pom.xml
new file mode 100644
index 0000000..dec4256
--- /dev/null
+++ b/microprofile-extensions/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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">
+  <parent>
+    <artifactId>geronimo-microprofile</artifactId>
+    <groupId>org.apache.geronimo</groupId>
+    <version>1.0.2-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>microprofile-extensions</artifactId>
+  <name>Geronimo Microprofile :: Extensions</name>
+  <packaging>pom</packaging>
+  <description>A parent pom for microprofile extensions.</description>
+
+  <modules>
+    <module>microprofile-extensions-config</module>
+  </modules>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <version>5.4.0</version>
+      <scope>test</scope>
+      <type>pom</type>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/pom.xml b/pom.xml
index 4feac94..1244654 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,7 +34,10 @@
   </description>
 
   <properties>
-    <meecrowave.version>1.2.5</meecrowave.version>
+    <meecrowave.version>1.2.6</meecrowave.version>
+
+    <microprofile-config-api.version>1.3</microprofile-config-api.version>
+    <geronimo-config.version>1.2.1</geronimo-config.version>
   </properties>
 
   <modules>
@@ -42,6 +45,7 @@
     <module>utilda</module>
     <module>geronimo-microprofile-site</module>
     <module>geronimo-microprofile-reporter</module>
+    <module>microprofile-extensions</module>
   </modules>
 
   <scm>