secrets-property-source: add support for reading secrets from volume mounts
This commit is contained in:
committed by
Ioannis Canellos
parent
bbd59ade34
commit
84a94532d3
64
readme.md
64
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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user