diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/bootstrap/stubs/BootstrapKubernetesClientSanitizeEnvEndpointStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/bootstrap/stubs/BootstrapKubernetesClientSanitizeEnvEndpointStub.java new file mode 100644 index 0000000000..872120e04b --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/bootstrap/stubs/BootstrapKubernetesClientSanitizeEnvEndpointStub.java @@ -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)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeConfigpropsEndpointTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeConfigpropsEndpointTests.java new file mode 100644 index 0000000000..585747b5a5 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeConfigpropsEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeEnvEndpointTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeEnvEndpointTests.java new file mode 100644 index 0000000000..98fca219b4 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/BootstrapKubernetesClientSanitizeEnvEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java new file mode 100644 index 0000000000..c1100c04ce --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataKubernetesClientSanitizeEnvEndpointTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataKubernetesClientSanitizeEnvEndpointTests.java new file mode 100644 index 0000000000..07156c3f1f --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataKubernetesClientSanitizeEnvEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataSanitize.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataSanitize.java new file mode 100644 index 0000000000..974e5ae181 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/ConfigDataSanitize.java @@ -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 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(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeApp.java new file mode 100644 index 0000000000..22b88146e2 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeApp.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeController.java new file mode 100644 index 0000000000..37d63a392e --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeController.java @@ -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(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeProperties.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeProperties.java new file mode 100644 index 0000000000..2ad2eaf224 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/sanitize_secrets/SanitizeProperties.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories index d10d1d904d..f453b1b3bd 100644 --- a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories @@ -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 diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/sanitize-two.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/sanitize-two.yaml new file mode 100644 index 0000000000..7037403969 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/sanitize-two.yaml @@ -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 diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/sanitize.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/sanitize.yaml new file mode 100644 index 0000000000..f91869d894 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/sanitize.yaml @@ -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 diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnSanitizeSecrets.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnSanitizeSecrets.java new file mode 100644 index 0000000000..0e3d6849a0 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/ConditionalOnSanitizeSecrets.java @@ -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 + * spring.cloud.kubernetes.sanitize.secrets. + * + * @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"; + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfiguration.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfiguration.java new file mode 100644 index 0000000000..1244d1b3dd --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfiguration.java @@ -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> 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; + }; + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-kubernetes-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 6708f3f4bd..1b5dcabb6f 100644 --- a/spring-cloud-kubernetes-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-kubernetes-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfigurationTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfigurationTests.java new file mode 100644 index 0000000000..b7dd4a6f7a --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/KubernetesCommonsSanitizeAutoConfigurationTests.java @@ -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; + + } + +} diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/SanitizeTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/SanitizeTests.java new file mode 100644 index 0000000000..ba87eac7c3 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/SanitizeTests.java @@ -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 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"); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java index a0c72da351..69687f6f6d 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java @@ -46,11 +46,11 @@ * @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 diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeConfigpropsEndpointTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeConfigpropsEndpointTests.java new file mode 100644 index 0000000000..b6c5aa516e --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeConfigpropsEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeEnvEndpointTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeEnvEndpointTests.java new file mode 100644 index 0000000000..fe7fe397b7 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/BootstrapFabric8SanitizeEnvEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java new file mode 100644 index 0000000000..52a002630a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8ConfigpropsEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8SanitizeEnvEndpointTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8SanitizeEnvEndpointTests.java new file mode 100644 index 0000000000..4230604bb6 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/ConfigDataFabric8SanitizeEnvEndpointTests.java @@ -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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + + /** + *
+	 *     - 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.
+	 *
+	 * 
+ */ + @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"); + } + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/Fabric8SecretsSanitize.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/Fabric8SecretsSanitize.java new file mode 100644 index 0000000000..ec8c389d95 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/Fabric8SecretsSanitize.java @@ -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(); + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeApp.java new file mode 100644 index 0000000000..c7a48b7c00 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeApp.java @@ -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); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeController.java new file mode 100644 index 0000000000..0f609e2fc9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeController.java @@ -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(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeProperties.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeProperties.java new file mode 100644 index 0000000000..83f0c350c0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/sanitize_secrets/SanitizeProperties.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_property_source/ConfigDataFabric8SecretsPropertySourceTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_property_source/ConfigDataFabric8SecretsPropertySourceTest.java index 44ba02775f..9ff3a136e8 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_property_source/ConfigDataFabric8SecretsPropertySourceTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_property_source/ConfigDataFabric8SecretsPropertySourceTest.java @@ -19,14 +19,11 @@ 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") diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize-two.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize-two.yaml new file mode 100644 index 0000000000..7037403969 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize-two.yaml @@ -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 diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize.yaml new file mode 100644 index 0000000000..f91869d894 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/sanitize.yaml @@ -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