Merge branch '3.0.x'

This commit is contained in:
Ryan Baxter
2023-12-27 14:48:36 -05:00
29 changed files with 2727 additions and 6 deletions

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.boostrap.stubs;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1SecretBuilder;
import io.kubernetes.client.openapi.models.V1SecretList;
import io.kubernetes.client.util.ClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap
* context; with some stub data already present.
*
* @author wind57
*/
@Order(0)
@Configuration
@ConditionalOnProperty("bootstrap.sanitize")
public class BootstrapKubernetesClientSanitizeEnvEndpointStub {
@Bean
public WireMockServer wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
return server;
}
@Bean
public ApiClient apiClient(WireMockServer wireMockServer) {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build();
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
apiClient.setDebugging(true);
stubData();
return apiClient;
}
public static void stubData() {
V1ConfigMap one = new V1ConfigMapBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("sanitize-configmap").withNamespace("test").build())
.addToData(Map.of("sanitize.sanitizeConfigMapName", "sanitizeConfigMapValue")).build();
V1Secret secretOne = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("sanitize-secret").withNamespace("test").build())
.addToData(Map.of("sanitize.sanitizeSecretName", "sanitizeSecretValue".getBytes())).build();
V1Secret secretTwo = new V1SecretBuilder()
.withMetadata(new V1ObjectMetaBuilder().withName("sanitize-secret-two").withNamespace("test").build())
.addToData(Map.of("sanitize.sanitizeSecretNameTwo", "sanitizeSecretValueTwo".getBytes())).build();
// the actual stub for CoreV1Api calls
V1ConfigMapList configMapList = new V1ConfigMapList();
configMapList.addItemsItem(one);
V1SecretList secretList = new V1SecretList();
secretList.addItemsItem(secretOne);
secretList.addItemsItem(secretTwo);
WireMock.stubFor(WireMock.get("/api/v1/namespaces/test/configmaps")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMapList))));
WireMock.stubFor(WireMock.get("/api/v1/namespaces/test/secrets")
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secretList))));
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class BootstrapKubernetesClientSanitizeConfigpropsEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class DefaultSettingsTest {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.configprops.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.configprops.show-values=NEVER", "bootstrap.sanitize=true",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class ExplicitNever {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=false", "bootstrap.sanitize=true",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithoutSanitizingFunction {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=true", "bootstrap.sanitize=true",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithSanitizingFunction {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretNameTwo")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class BootstrapKubernetesClientSanitizeEnvEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class DefaultSettingsTest {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.env.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.env.show-values=NEVER", "bootstrap.sanitize=true",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class ExplicitNever {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false",
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithoutSanitizingFunction {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true",
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithSanitizingFunction {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class ConfigDataFabric8ConfigpropsEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@Nested
class DefaultSettingsTest extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.configprops.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=NEVER",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@Nested
class ExplicitNever extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=false",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@Nested
class AlwaysWithoutSanitizingFunction extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=true",
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml" })
@Nested
class AlwaysWithSanitizingFunction extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretNameTwo")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class ConfigDataKubernetesClientSanitizeEnvEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoints.web.exposure.include=*", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class DefaultSettingsTest extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.env.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoint.env.show-values=NEVER", "spring.cloud.kubernetes.client.namespace=test" })
@Nested
class ExplicitNever extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithoutSanitizingFunction extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true",
"spring.cloud.kubernetes.client.namespace=test" })
@Nested
class AlwaysWithSanitizingFunction extends ConfigDataSanitize {
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.kubernetes.client.util.ClientBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.BootstrapKubernetesClientSanitizeEnvEndpointStub.stubData;
/**
* @author wind57
*/
abstract class ConfigDataSanitize {
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
@BeforeAll
static void wireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
WireMock.configureFor("localhost", server.port());
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
clientUtilsMock
.when(() -> KubernetesClientUtils.getApplicationNamespace(Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn("test");
stubData();
}
@AfterAll
static void teardown() {
clientUtilsMock.close();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author wind57
*/
@EnableConfigurationProperties(SanitizeProperties.class)
@SpringBootApplication
class SanitizeApp {
public static void main(String[] args) {
SpringApplication.run(SanitizeApp.class, args);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wind57
*/
@RestController
class SanitizeController {
private final SanitizeProperties sanitizeProperties;
SanitizeController(SanitizeProperties sanitizeProperties) {
this.sanitizeProperties = sanitizeProperties;
}
@GetMapping("/secret")
String secret() {
return sanitizeProperties.getSanitizeSecretName();
}
@GetMapping("/configmap")
String configmap() {
return sanitizeProperties.getSanitizeConfigMapName();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.client.config.sanitize_secrets;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author wind57
*/
@ConfigurationProperties("sanitize")
class SanitizeProperties {
private String sanitizeSecretName;
private String sanitizeSecretNameTwo;
private String sanitizeConfigMapName;
public String getSanitizeSecretName() {
return sanitizeSecretName;
}
public void setSanitizeSecretName(String sanitizeSecretName) {
this.sanitizeSecretName = sanitizeSecretName;
}
public String getSanitizeConfigMapName() {
return sanitizeConfigMapName;
}
public void setSanitizeConfigMapName(String sanitizeConfigMapName) {
this.sanitizeConfigMapName = sanitizeConfigMapName;
}
public String getSanitizeSecretNameTwo() {
return sanitizeSecretNameTwo;
}
public void setSanitizeSecretNameTwo(String sanitizeSecretNameTwo) {
this.sanitizeSecretNameTwo = sanitizeSecretNameTwo;
}
}

View File

@@ -11,4 +11,5 @@ org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.SingleSourceM
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.ConfigMapNameAsPrefixConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.SourcesOrderConfigurationStub, \
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.BootstrapKubernetesClientSanitizeEnvEndpointStub, \
org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration

View File

@@ -0,0 +1,16 @@
spring:
application:
name: sanitize
cloud:
kubernetes:
secrets:
enableApi: true
sources:
- name: sanitize-secret
namespaces: test
- name: sanitize-secret-two
namespaces: test
config:
sources:
- name: sanitize-configmap
namespace: test

View File

@@ -0,0 +1,14 @@
spring:
application:
name: sanitize
cloud:
kubernetes:
secrets:
enableApi: true
sources:
- name: sanitize-secret
namespaces: test
config:
sources:
- name: sanitize-configmap
namespace: test

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.commons;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Provides a more succinct conditional
* <code>spring.cloud.kubernetes.sanitize.secrets</code>.
*
* @author wind57
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ConditionalOnProperty(value = ConditionalOnSanitizeSecrets.VALUE, matchIfMissing = false)
public @interface ConditionalOnSanitizeSecrets {
/**
* Conditional value to use.
*/
String VALUE = "spring.cloud.kubernetes.sanitize.secrets";
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.commons;
import java.util.Collection;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.PropertySource;
/**
* @author wind57
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.SanitizableData")
@ConditionalOnSanitizeSecrets
class KubernetesCommonsSanitizeAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SanitizingFunction secretsPropertySourceSanitizingFunction() {
return data -> {
PropertySource<?> propertySource = data.getPropertySource();
if (propertySource instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
PropertySource<?> source = bootstrapPropertySource.getDelegate();
if (source instanceof SecretsPropertySource) {
return new SanitizableData(propertySource, data.getKey(), data.getValue())
.withValue(SanitizableData.SANITIZED_VALUE);
}
}
if (propertySource instanceof SecretsPropertySource) {
return new SanitizableData(propertySource, data.getKey(), data.getValue())
.withValue(SanitizableData.SANITIZED_VALUE);
}
// at the moment, our structure is pretty simply, CompositePropertySource
// children can be SecretsPropertySource; i.e.: there is no recursion
// needed to get all children. If this structure changes, there are enough
// unit tests that will start failing.
if (propertySource instanceof CompositePropertySource compositePropertySource) {
Collection<PropertySource<?>> sources = compositePropertySource.getPropertySources();
for (PropertySource<?> one : sources) {
if (one.containsProperty(data.getKey()) && one instanceof SecretsPropertySource) {
return new SanitizableData(propertySource, data.getKey(), data.getValue())
.withValue(SanitizableData.SANITIZED_VALUE);
}
}
}
return data;
};
}
}

View File

@@ -1,4 +1,5 @@
org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration
org.springframework.cloud.kubernetes.commons.KubernetesCommonsSanitizeAutoConfiguration
org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadAutoConfiguration
org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadPropertiesAutoConfiguration
org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.commons;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author wind57
*/
class KubernetesCommonsSanitizeAutoConfigurationTests {
@Test
void sanitizeSecretsNotEnabled() {
contextRunner(false, false)
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).isEmpty());
}
@Test
void sanitizeSecretsEnabled() {
contextRunner(true, false)
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).hasSize(1));
}
@Test
void sanitizeSecretsEnabledSanitizedClassNotPresent() {
contextRunner(true, true)
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).isEmpty());
}
private ApplicationContextRunner contextRunner(boolean enableSanitizeSecrets, boolean filterSanitizedDataClass) {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(KubernetesCommonsSanitizeAutoConfiguration.class));
if (enableSanitizeSecrets) {
contextRunner = contextRunner.withPropertyValues(ConditionalOnSanitizeSecrets.VALUE + "=true");
}
if (filterSanitizedDataClass) {
contextRunner = contextRunner.withClassLoader(new FilteredClassLoader(SanitizableData.class));
}
contextRunner = contextRunner.withPropertyValues("spring.main.cloud-platform=KUBERNETES");
return contextRunner;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.commons;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
import org.springframework.cloud.kubernetes.commons.config.SourceData;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
import static org.springframework.boot.actuate.endpoint.SanitizableData.SANITIZED_VALUE;
/**
* @author wind57
*/
class SanitizeTests {
private static final boolean SHOW_UNSANITIZED = true;
private static final List<SanitizingFunction> SANITIZING_FUNCTIONS = List
.of(new KubernetesCommonsSanitizeAutoConfiguration().secretsPropertySourceSanitizingFunction());
@Test
void bootstrapPropertySourceNotSecrets() {
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
new EnumerablePropertySource<>("enumerable") {
@Override
public String[] getPropertyNames() {
return new String[0];
}
@Override
public Object getProperty(String name) {
return null;
}
});
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), "xyz");
}
@Test
void bootstrapPropertySourceSecrets() {
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
new SecretsPropertySource(new SourceData("secret-source", Map.of())));
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), SANITIZED_VALUE);
}
@Test
void notSecretsPropertySource() {
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
new MountConfigMapPropertySource("mount-source", Map.of()));
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), "xyz");
}
@Test
void secretsPropertySource() {
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
new SecretsPropertySource(new SourceData("secret-source", Map.of())));
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), SANITIZED_VALUE);
}
@Test
void compositeOneSecretOneMount() {
CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
compositePropertySource.addFirstPropertySource(
new SecretsPropertySource(new SourceData("secret-source", Map.of("secret", "xyz"))));
compositePropertySource
.addFirstPropertySource(new MountConfigMapPropertySource("mount-source", Map.of("mount", "abc")));
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
SanitizableData sanitizableDataSecret = new SanitizableData(compositePropertySource, "secret", "xyz");
SanitizableData sanitizableDataMount = new SanitizableData(compositePropertySource, "mount", "abc");
Assertions.assertEquals(sanitizer.sanitize(sanitizableDataSecret, SHOW_UNSANITIZED), SANITIZED_VALUE);
Assertions.assertEquals(sanitizer.sanitize(sanitizableDataMount, SHOW_UNSANITIZED), "abc");
}
}

View File

@@ -46,11 +46,11 @@ import org.springframework.core.env.Environment;
* @author Ioannis Canellos
*/
@Configuration(proxyBeanMethods = false)
@Import({ KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class })
@ConditionalOnClass({ ConfigMap.class, Secret.class })
@AutoConfigureAfter(KubernetesBootstrapConfiguration.class)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@ConditionalOnBootstrapEnabled
@ConditionalOnClass({ ConfigMap.class, Secret.class })
@Import({ KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class })
@AutoConfigureAfter(KubernetesBootstrapConfiguration.class)
public class Fabric8BootstrapConfiguration {
@Bean

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class BootstrapFabric8SanitizeConfigpropsEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class DefaultSettingsTest extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.configprops.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.configprops.show-values=NEVER" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class ExplicitNever extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=false" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=true" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretNameTwo")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class BootstrapFabric8SanitizeEnvEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class DefaultSettingsTest extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.env.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.env.show-values=NEVER" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class ExplicitNever extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class ConfigDataFabric8ConfigpropsEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class DefaultSettingsTest extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.configprops.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=NEVER",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class ExplicitNever extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=false",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.configprops.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"management.endpoint.configprops.show-values=ALWAYS",
"spring.cloud.kubernetes.sanitize.secrets=true",
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretNameTwo")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.SanitizableData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* @author wind57
*/
class ConfigDataFabric8SanitizeEnvEndpointTests {
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoints.web.exposure.include=*" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class DefaultSettingsTest extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
// management.endpoint.env.show-values = NEVER
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoint.env.show-values=NEVER" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class ExplicitNever extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = false
*
* Sanitizing functions must apply, but we have none registered, as such
* everything is visible in plain text, both from configmaps and secrets.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// secret is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo("sanitizeSecretValue");
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
/**
* <pre>
* - management.endpoint.env.show-values = ALWAYS
* - spring.cloud.kubernetes.sanitize.secrets = true
*
* Sanitizing functions must apply, and we have one registered, as such
* configmap is visible in plain text, but secrets are sanitized.
*
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml",
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true" })
@EnableKubernetesMockClient(crud = true, https = false)
@Nested
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
private static KubernetesClient mockClient;
@Autowired
private WebTestClient webClient;
@LocalManagementPort
private int port;
@BeforeAll
static void setUpBeforeClass() {
setUpBeforeClass(mockClient);
}
@Test
void test() {
// configmap is not sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
.isEqualTo("sanitizeConfigMapValue");
// first secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// second secret is sanitized
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
.isEqualTo(SanitizableData.SANITIZED_VALUE);
// secret is usable from configuration properties
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
// configmap is usable from configuration properties
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import java.util.Base64;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
/**
* @author wind57
*/
abstract class Fabric8SecretsSanitize {
private static final String NAMESPACE = "test";
static void setUpBeforeClass(KubernetesClient mockClient) {
// Configure kubernetes master url to point to the mock server
System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl());
System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, NAMESPACE);
System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
Secret secret = new SecretBuilder().withNewMetadata().withName("sanitize-secret").endMetadata()
.addToData("sanitize.sanitizeSecretName",
Base64.getEncoder().encodeToString("sanitizeSecretValue".getBytes()))
.build();
mockClient.secrets().inNamespace(NAMESPACE).resource(secret).create();
Secret secretTwo = new SecretBuilder().withNewMetadata().withName("sanitize-secret-two").endMetadata()
.addToData("sanitize.sanitizeSecretNameTwo",
Base64.getEncoder().encodeToString("sanitizeSecretValueTwo".getBytes()))
.build();
mockClient.secrets().inNamespace(NAMESPACE).resource(secretTwo).create();
ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("sanitize-configmap").endMetadata()
.addToData("sanitize.sanitizeConfigMapName", "sanitizeConfigMapValue").build();
mockClient.configMaps().inNamespace(NAMESPACE).resource(configMap).create();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author wind57
*/
@EnableConfigurationProperties(SanitizeProperties.class)
@SpringBootApplication
class SanitizeApp {
public static void main(String[] args) {
SpringApplication.run(SanitizeApp.class, args);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wind57
*/
@RestController
class SanitizeController {
private final SanitizeProperties sanitizeProperties;
SanitizeController(SanitizeProperties sanitizeProperties) {
this.sanitizeProperties = sanitizeProperties;
}
@GetMapping("/secret")
String secret() {
return sanitizeProperties.getSanitizeSecretName();
}
@GetMapping("/configmap")
String configmap() {
return sanitizeProperties.getSanitizeConfigMapName();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author wind57
*/
@ConfigurationProperties("sanitize")
class SanitizeProperties {
private String sanitizeSecretName;
private String sanitizeSecretNameTwo;
private String sanitizeConfigMapName;
public String getSanitizeSecretName() {
return sanitizeSecretName;
}
public void setSanitizeSecretName(String sanitizeSecretName) {
this.sanitizeSecretName = sanitizeSecretName;
}
public String getSanitizeConfigMapName() {
return sanitizeConfigMapName;
}
public void setSanitizeConfigMapName(String sanitizeConfigMapName) {
this.sanitizeConfigMapName = sanitizeConfigMapName;
}
public String getSanitizeSecretNameTwo() {
return sanitizeSecretNameTwo;
}
public void setSanitizeSecretNameTwo(String sanitizeSecretNameTwo) {
this.sanitizeSecretNameTwo = sanitizeSecretNameTwo;
}
}

View File

@@ -19,14 +19,11 @@ package org.springframework.cloud.kubernetes.fabric8.config.secrets_property_sou
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.kubernetes.fabric8.config.example.App;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class,
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" })
@TestPropertySource("classpath:/application-secrets.properties")

View File

@@ -0,0 +1,16 @@
spring:
application:
name: sanitize
cloud:
kubernetes:
secrets:
enableApi: true
sources:
- name: sanitize-secret
namespaces: test
- name: sanitize-secret-two
namespaces: test
config:
sources:
- name: sanitize-configmap
namespace: test

View File

@@ -0,0 +1,14 @@
spring:
application:
name: sanitize
cloud:
kubernetes:
secrets:
enableApi: true
sources:
- name: sanitize-secret
namespaces: test
config:
sources:
- name: sanitize-configmap
namespace: test