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
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+ * - 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