secrets-property-source: add support for reading secrets from volume mounts

This commit is contained in:
lburgazzoli
2016-10-03 22:15:59 +02:00
committed by Ioannis Canellos
parent bbd59ade34
commit 84a94532d3
4 changed files with 137 additions and 46 deletions

View File

@@ -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

View File

@@ -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<String, String> labels = new HashMap<>();
private List<String> 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<String, String> labels) {
this.labels = labels;
}
public Map<String, String> getLabels() {
return labels;
}
public void setPaths(List<String> paths) {
this.paths = paths;
}
public List<String> getPaths() {
return paths;
}
}

View File

@@ -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<String, Object> getSourceData(KubernetesClient client, Environment env, SecretsConfigProperties config) {
String name = getApplicationName(env, config);
String namespace = getApplicationNamespace(client, config);
String namespace = getApplicationNamespace(client, env, config);
Map<String, Object> 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<String, Object> 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<String, Object> result) {
try {
result.put(
path.getFileName().toString(),
new String(Files.readAllBytes(path)).trim());
} catch (IOException e) {
LOGGER.warn("", e);
}
}
}

View File

@@ -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 {