diff --git a/readme.md b/readme.md index f1c7f987..593a53d6 100644 --- a/readme.md +++ b/readme.md @@ -100,11 +100,21 @@ data: max:16 ``` +Notes: +- To access ConfigMaps on OpenShift the service account needs at least view permissions i.e.: + + ```oc policy add-role-to-user view system:serviceaccount:$(oc project -q):default -n $(oc project -q)``` + #### Secrets PropertySource Kubernetes has the notion of [Secrets](http://kubernetes.io/docs/user-guide/secrets/) for storing sensitive data such as password, OAuth tokens, etc. This project provides integration with `Secrets` to make secrets accessible by spring boot. -The `Secrets` `PropertySource` when enabled will lookup Kubernetes for `Secrets` named after the application (see `spring.application.name`) or matching some labels. If the secrets are found theirs data is made available to the application. +The `Secrets` `PropertySource` when enabled will lookup Kubernetes for `Secrets` from the following sources: +- named after the application (see `spring.application.name`) +- matching some labels +- reading recursively from secrets mounts + +If the secrets are found theirs data is made available to the application. Example: @@ -145,29 +155,45 @@ This can be externalized to Secrets in yaml format: amq.password: cGdhZG1pbgo= ``` -You can select the Secrets to consume by defining a list of labels: -``` --Dspring.cloud.kubernetes.secrets.labels.broker=activemq --Dspring.cloud.kubernetes.secrets.labels.db=postgres -``` +You can select the Secrets to consume in a number of ways: + +1. By defining a list of labels: + ``` + -Dspring.cloud.kubernetes.secrets.labels.broker=activemq + -Dspring.cloud.kubernetes.secrets.labels.db=postgres + ``` + +2. By setting a named secret: + ``` + -Dspring.cloud.kubernetes.secrets.name=postgres-secrets + ``` + +3. By listing the directories were secrets are mapped: + ``` + -Dspring.cloud.kubernetes.secrets.paths=/etc/secrets/activemq,etc/secrets/postgres + ``` + + If you have all the secrets mapped to a common root, you can set them like: + + ``` + -Dspring.cloud.kubernetes.secrets.paths=/etc/secrets + ``` -If you have a single secret you can look it up by its name, i.e: -``` --Dspring.cloud.kubernetes.secrets.name=postgres-secrets -``` Properties: -| Name | Type | Default | Description -| --- | --- | --- | --- -| spring.cloud.kubernetes.secrets.enabled | Boolean | true | Enable Secrets PropertySource -| spring.cloud.kubernetes.secrets.name | String | ${spring.application.name} | Sets the name of the secret to lookup -| spring.cloud.kubernetes.secrets.labels | Map | null | Sets the labels used to lookup secrets - -Note: -- If labels are set, name is discarded. -- The property spring.cloud.kubernetes.secrets.labels behave as defined by [Map-based binding]( https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding#map-based-binding) +| Name | Type | Default | Description +| --- | --- | --- | --- +| spring.cloud.kubernetes.secrets.enabled | Boolean | true | Enable Secrets PropertySource +| spring.cloud.kubernetes.secrets.name | String | ${spring.application.name} | Sets the name of the secret to lookup +| spring.cloud.kubernetes.secrets.labels | Map | null | Sets the labels used to lookup secrets +| spring.cloud.kubernetes.secrets.paths | List | null | Sets the paths were secrets are mounted +| spring.cloud.kubernetes.secrets.enableApi | Boolean | false | Enable/Disable consuming secrets via APIs +Notes: +- The property spring.cloud.kubernetes.secrets.labels behave as defined by [Map-based binding](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding#map-based-binding) +- The property spring.cloud.kubernetes.secrets.paths behave as defined by [Collection-based binding](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding#collection-based-binding) +- Access to secrets via API may be restricted, the preferred way is to mount secret to the POD ### Pod Health Indicator diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsConfigProperties.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsConfigProperties.java index 5d5f6b55..b9b67d74 100644 --- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsConfigProperties.java +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsConfigProperties.java @@ -17,17 +17,22 @@ package io.fabric8.spring.cloud.kubernetes.config; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.env.Environment; @ConfigurationProperties("spring.cloud.kubernetes.secrets") public class SecretsConfigProperties { private boolean enabled = true; + private boolean enableApi = false; private String name; private String namespace; private Map labels = new HashMap<>(); + private List paths = new LinkedList<>(); public boolean isEnabled() { return enabled; @@ -37,10 +42,22 @@ public class SecretsConfigProperties { this.enabled = enabled; } + public boolean isEnableApi() { + return enableApi; + } + + public void setEnableApi(boolean enableApi) { + this.enableApi = enableApi; + } + public String getName() { return name; } + public String getName(Environment env) { + return name; + } + public void setName(String name) { this.name = name; } @@ -53,7 +70,19 @@ public class SecretsConfigProperties { this.namespace = namespace; } + public void setLabels(Map labels) { + this.labels = labels; + } + public Map getLabels() { return labels; } + + public void setPaths(List paths) { + this.paths = paths; + } + + public List getPaths() { + return paths; + } } diff --git a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySource.java b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySource.java index 570eb007..4a9af6f9 100644 --- a/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySource.java +++ b/spring-cloud-kubernetes-core/src/main/java/io/fabric8/spring/cloud/kubernetes/config/SecretsPropertySource.java @@ -16,6 +16,10 @@ */ package io.fabric8.spring.cloud.kubernetes.config; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -46,17 +50,18 @@ public class SecretsPropertySource extends MapPropertySource { .append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR) .append(getApplicationName(env,config)) .append(Constants.PROPERTY_SOURCE_NAME_SEPARATOR) - .append(getApplicationNamespace(client, config)) + .append(getApplicationNamespace(client, env, config)) .toString(); } private static Map getSourceData(KubernetesClient client, Environment env, SecretsConfigProperties config) { String name = getApplicationName(env, config); - String namespace = getApplicationNamespace(client, config); - + String namespace = getApplicationNamespace(client, env, config); Map result = new HashMap<>(); - try { - if (config.getLabels().isEmpty()) { + + if (config.isEnableApi()) { + try { + // Read for secrets api (named) if (StringUtils.isEmpty(namespace)) { putAll( client.secrets() @@ -71,30 +76,40 @@ public class SecretsPropertySource extends MapPropertySource { .get(), result); } - } else { - if (StringUtils.isEmpty(namespace)) { - client.secrets() - .withLabels(config.getLabels()) - .list() - .getItems() - .forEach(s -> putAll(s, result)); - } else { - client.secrets() - .inNamespace(namespace) - .withLabels(config.getLabels()) - .list() - .getItems() - .forEach(s -> putAll(s, result)); + // Read for secrets api (label) + if (!config.getLabels().isEmpty()) { + if (StringUtils.isEmpty(namespace)) { + client.secrets() + .withLabels(config.getLabels()) + .list() + .getItems() + .forEach(s -> putAll(s, result)); + } else { + client.secrets() + .inNamespace(namespace) + .withLabels(config.getLabels()) + .list() + .getItems() + .forEach(s -> putAll(s, result)); + } } + } catch (Exception e) { + LOGGER.warn("Can't read secret with name: [{}] or labels [{}] in namespace:[{}] (cause: {}). Ignoring", + name, + config.getLabels(), + namespace, + e.getMessage()); } - } catch (Exception e) { - LOGGER.warn("Can't read secret with name: [{}] or labels [{}] in namespace:[{}]. Ignoring", - name, - config.getLabels(), - namespace, - e); } + + // read for secrets mount + config.getPaths() + .stream() + .map(Paths::get) + .filter(Files::exists) + .forEach(p -> putAll(p, result)); + return result; } @@ -117,7 +132,7 @@ public class SecretsPropertySource extends MapPropertySource { return name; } - private static String getApplicationNamespace(KubernetesClient client, SecretsConfigProperties config) { + private static String getApplicationNamespace(KubernetesClient client, Environment env, SecretsConfigProperties config) { String namespace = config.getNamespace(); if (StringUtils.isEmpty(namespace)) { LOGGER.debug("Secret namespace has not been set, taking it from client (ns={})", @@ -137,4 +152,24 @@ public class SecretsPropertySource extends MapPropertySource { ); } } + + private static void putAll(Path path, Map result) { + try { + Files.walk(path) + .filter(Files::isRegularFile) + .forEach(p -> readFile(p, result)); + } catch (IOException e) { + LOGGER.warn("", e); + } + } + + private static void readFile(Path path, Map result) { + try { + result.put( + path.getFileName().toString(), + new String(Files.readAllBytes(path)).trim()); + } catch (IOException e) { + LOGGER.warn("", e); + } + } } diff --git a/spring-cloud-kubernetes-core/src/test/groovy/io/fabric8/spring/cloud/kubernetes/config/test/CoreTest.groovy b/spring-cloud-kubernetes-core/src/test/groovy/io/fabric8/spring/cloud/kubernetes/config/test/CoreTest.groovy index 97c622f2..bff4f97c 100644 --- a/spring-cloud-kubernetes-core/src/test/groovy/io/fabric8/spring/cloud/kubernetes/config/test/CoreTest.groovy +++ b/spring-cloud-kubernetes-core/src/test/groovy/io/fabric8/spring/cloud/kubernetes/config/test/CoreTest.groovy @@ -36,7 +36,8 @@ import groovy.util.logging.Slf4j "spring.application.name=testapp", "spring.cloud.kubernetes.client.namespace=testns", "spring.cloud.kubernetes.client.trustCerts=true", - "spring.cloud.kubernetes.config.namespace=testns" + "spring.cloud.kubernetes.config.namespace=testns", + "spring.cloud.kubernetes.secrets.enableApi=true" ]) @EnableConfigurationProperties class CoreTest extends Specification {