Merge branch '3.0.x'
This commit is contained in:
@@ -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))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class BootstrapKubernetesClientSanitizeConfigpropsEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class DefaultSettingsTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.configprops.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.configprops.show-values=NEVER", "bootstrap.sanitize=true",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class ExplicitNever {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=false", "bootstrap.sanitize=true",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=true", "bootstrap.sanitize=true",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretNameTwo")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class BootstrapKubernetesClientSanitizeEnvEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class DefaultSettingsTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.env.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.env.show-values=NEVER", "bootstrap.sanitize=true",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class ExplicitNever {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false",
|
||||
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true",
|
||||
"bootstrap.sanitize=true", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class ConfigDataFabric8ConfigpropsEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@Nested
|
||||
class DefaultSettingsTest extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.configprops.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=NEVER",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@Nested
|
||||
class ExplicitNever extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=false",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=true",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml" })
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretNameTwo")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class ConfigDataKubernetesClientSanitizeEnvEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class DefaultSettingsTest extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.env.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoint.env.show-values=NEVER", "spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class ExplicitNever extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true",
|
||||
"spring.cloud.kubernetes.client.namespace=test" })
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends ConfigDataSanitize {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientUtils;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.BootstrapKubernetesClientSanitizeEnvEndpointStub.stubData;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
abstract class ConfigDataSanitize {
|
||||
|
||||
private static MockedStatic<KubernetesClientUtils> clientUtilsMock;
|
||||
|
||||
@BeforeAll
|
||||
static void wireMock() {
|
||||
WireMockServer server = new WireMockServer(options().dynamicPort());
|
||||
server.start();
|
||||
WireMock.configureFor("localhost", server.port());
|
||||
clientUtilsMock = mockStatic(KubernetesClientUtils.class);
|
||||
clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient)
|
||||
.thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build());
|
||||
clientUtilsMock
|
||||
.when(() -> KubernetesClientUtils.getApplicationNamespace(Mockito.any(), Mockito.any(), Mockito.any()))
|
||||
.thenReturn("test");
|
||||
stubData();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void teardown() {
|
||||
clientUtilsMock.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@EnableConfigurationProperties(SanitizeProperties.class)
|
||||
@SpringBootApplication
|
||||
class SanitizeApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SanitizeApp.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@RestController
|
||||
class SanitizeController {
|
||||
|
||||
private final SanitizeProperties sanitizeProperties;
|
||||
|
||||
SanitizeController(SanitizeProperties sanitizeProperties) {
|
||||
this.sanitizeProperties = sanitizeProperties;
|
||||
}
|
||||
|
||||
@GetMapping("/secret")
|
||||
String secret() {
|
||||
return sanitizeProperties.getSanitizeSecretName();
|
||||
}
|
||||
|
||||
@GetMapping("/configmap")
|
||||
String configmap() {
|
||||
return sanitizeProperties.getSanitizeConfigMapName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.sanitize_secrets;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@ConfigurationProperties("sanitize")
|
||||
class SanitizeProperties {
|
||||
|
||||
private String sanitizeSecretName;
|
||||
|
||||
private String sanitizeSecretNameTwo;
|
||||
|
||||
private String sanitizeConfigMapName;
|
||||
|
||||
public String getSanitizeSecretName() {
|
||||
return sanitizeSecretName;
|
||||
}
|
||||
|
||||
public void setSanitizeSecretName(String sanitizeSecretName) {
|
||||
this.sanitizeSecretName = sanitizeSecretName;
|
||||
}
|
||||
|
||||
public String getSanitizeConfigMapName() {
|
||||
return sanitizeConfigMapName;
|
||||
}
|
||||
|
||||
public void setSanitizeConfigMapName(String sanitizeConfigMapName) {
|
||||
this.sanitizeConfigMapName = sanitizeConfigMapName;
|
||||
}
|
||||
|
||||
public String getSanitizeSecretNameTwo() {
|
||||
return sanitizeSecretNameTwo;
|
||||
}
|
||||
|
||||
public void setSanitizeSecretNameTwo(String sanitizeSecretNameTwo) {
|
||||
this.sanitizeSecretNameTwo = sanitizeSecretNameTwo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,4 +11,5 @@ org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.SingleSourceM
|
||||
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \
|
||||
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.ConfigMapNameAsPrefixConfigurationStub, \
|
||||
org.springframework.cloud.kubernetes.client.config.bootstrap.stubs.SourcesOrderConfigurationStub, \
|
||||
org.springframework.cloud.kubernetes.client.config.boostrap.stubs.BootstrapKubernetesClientSanitizeEnvEndpointStub, \
|
||||
org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* Provides a more succinct conditional
|
||||
* <code>spring.cloud.kubernetes.sanitize.secrets</code>.
|
||||
*
|
||||
* @author wind57
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@ConditionalOnProperty(value = ConditionalOnSanitizeSecrets.VALUE, matchIfMissing = false)
|
||||
public @interface ConditionalOnSanitizeSecrets {
|
||||
|
||||
/**
|
||||
* Conditional value to use.
|
||||
*/
|
||||
String VALUE = "spring.cloud.kubernetes.sanitize.secrets";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.SanitizableData")
|
||||
@ConditionalOnSanitizeSecrets
|
||||
class KubernetesCommonsSanitizeAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SanitizingFunction secretsPropertySourceSanitizingFunction() {
|
||||
return data -> {
|
||||
PropertySource<?> propertySource = data.getPropertySource();
|
||||
if (propertySource instanceof BootstrapPropertySource<?> bootstrapPropertySource) {
|
||||
PropertySource<?> source = bootstrapPropertySource.getDelegate();
|
||||
if (source instanceof SecretsPropertySource) {
|
||||
return new SanitizableData(propertySource, data.getKey(), data.getValue())
|
||||
.withValue(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (propertySource instanceof SecretsPropertySource) {
|
||||
return new SanitizableData(propertySource, data.getKey(), data.getValue())
|
||||
.withValue(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
// at the moment, our structure is pretty simply, CompositePropertySource
|
||||
// children can be SecretsPropertySource; i.e.: there is no recursion
|
||||
// needed to get all children. If this structure changes, there are enough
|
||||
// unit tests that will start failing.
|
||||
if (propertySource instanceof CompositePropertySource compositePropertySource) {
|
||||
Collection<PropertySource<?>> sources = compositePropertySource.getPropertySources();
|
||||
for (PropertySource<?> one : sources) {
|
||||
if (one.containsProperty(data.getKey()) && one instanceof SecretsPropertySource) {
|
||||
return new SanitizableData(propertySource, data.getKey(), data.getValue())
|
||||
.withValue(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.commons.KubernetesCommonsSanitizeAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadPropertiesAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class KubernetesCommonsSanitizeAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void sanitizeSecretsNotEnabled() {
|
||||
contextRunner(false, false)
|
||||
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeSecretsEnabled() {
|
||||
contextRunner(true, false)
|
||||
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitizeSecretsEnabledSanitizedClassNotPresent() {
|
||||
contextRunner(true, true)
|
||||
.run(context -> assertThat(context.getBeansOfType(SanitizingFunction.class)).isEmpty());
|
||||
}
|
||||
|
||||
private ApplicationContextRunner contextRunner(boolean enableSanitizeSecrets, boolean filterSanitizedDataClass) {
|
||||
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(KubernetesCommonsSanitizeAutoConfiguration.class));
|
||||
|
||||
if (enableSanitizeSecrets) {
|
||||
contextRunner = contextRunner.withPropertyValues(ConditionalOnSanitizeSecrets.VALUE + "=true");
|
||||
}
|
||||
|
||||
if (filterSanitizedDataClass) {
|
||||
contextRunner = contextRunner.withClassLoader(new FilteredClassLoader(SanitizableData.class));
|
||||
}
|
||||
|
||||
contextRunner = contextRunner.withPropertyValues("spring.main.cloud-platform=KUBERNETES");
|
||||
|
||||
return contextRunner;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.commons;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.cloud.bootstrap.config.BootstrapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.MountConfigMapPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource;
|
||||
import org.springframework.cloud.kubernetes.commons.config.SourceData;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
|
||||
import static org.springframework.boot.actuate.endpoint.SanitizableData.SANITIZED_VALUE;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class SanitizeTests {
|
||||
|
||||
private static final boolean SHOW_UNSANITIZED = true;
|
||||
|
||||
private static final List<SanitizingFunction> SANITIZING_FUNCTIONS = List
|
||||
.of(new KubernetesCommonsSanitizeAutoConfiguration().secretsPropertySourceSanitizingFunction());
|
||||
|
||||
@Test
|
||||
void bootstrapPropertySourceNotSecrets() {
|
||||
|
||||
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
|
||||
new EnumerablePropertySource<>("enumerable") {
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
|
||||
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
|
||||
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), "xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bootstrapPropertySourceSecrets() {
|
||||
|
||||
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
|
||||
new SecretsPropertySource(new SourceData("secret-source", Map.of())));
|
||||
|
||||
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
|
||||
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
|
||||
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void notSecretsPropertySource() {
|
||||
|
||||
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
|
||||
new MountConfigMapPropertySource("mount-source", Map.of()));
|
||||
|
||||
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
|
||||
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
|
||||
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), "xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void secretsPropertySource() {
|
||||
|
||||
BootstrapPropertySource<?> bootstrapPropertySource = new BootstrapPropertySource<>(
|
||||
new SecretsPropertySource(new SourceData("secret-source", Map.of())));
|
||||
|
||||
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
|
||||
SanitizableData sanitizableData = new SanitizableData(bootstrapPropertySource, "secret", "xyz");
|
||||
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableData, SHOW_UNSANITIZED), SANITIZED_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeOneSecretOneMount() {
|
||||
CompositePropertySource compositePropertySource = new CompositePropertySource("composite");
|
||||
compositePropertySource.addFirstPropertySource(
|
||||
new SecretsPropertySource(new SourceData("secret-source", Map.of("secret", "xyz"))));
|
||||
compositePropertySource
|
||||
.addFirstPropertySource(new MountConfigMapPropertySource("mount-source", Map.of("mount", "abc")));
|
||||
|
||||
Sanitizer sanitizer = new Sanitizer(SANITIZING_FUNCTIONS);
|
||||
SanitizableData sanitizableDataSecret = new SanitizableData(compositePropertySource, "secret", "xyz");
|
||||
SanitizableData sanitizableDataMount = new SanitizableData(compositePropertySource, "mount", "abc");
|
||||
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableDataSecret, SHOW_UNSANITIZED), SANITIZED_VALUE);
|
||||
Assertions.assertEquals(sanitizer.sanitize(sanitizableDataMount, SHOW_UNSANITIZED), "abc");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,11 +46,11 @@ import org.springframework.core.env.Environment;
|
||||
* @author Ioannis Canellos
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({ KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class })
|
||||
@ConditionalOnClass({ ConfigMap.class, Secret.class })
|
||||
@AutoConfigureAfter(KubernetesBootstrapConfiguration.class)
|
||||
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@ConditionalOnBootstrapEnabled
|
||||
@ConditionalOnClass({ ConfigMap.class, Secret.class })
|
||||
@Import({ KubernetesCommonsAutoConfiguration.class, Fabric8AutoConfiguration.class })
|
||||
@AutoConfigureAfter(KubernetesBootstrapConfiguration.class)
|
||||
public class Fabric8BootstrapConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class BootstrapFabric8SanitizeConfigpropsEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class DefaultSettingsTest extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.configprops.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.configprops.show-values=NEVER" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class ExplicitNever extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=false" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=true" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.['sanitize-1'].beans.[*].properties.sanitizeSecretNameTwo")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class BootstrapFabric8SanitizeEnvEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class DefaultSettingsTest extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.env.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.env.show-values=NEVER" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class ExplicitNever extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true",
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bootstrap.name=sanitize-two",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class ConfigDataFabric8ConfigpropsEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class DefaultSettingsTest extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.configprops.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=NEVER",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class ExplicitNever extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=false",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.configprops.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"management.endpoint.configprops.show-values=ALWAYS",
|
||||
"spring.cloud.kubernetes.sanitize.secrets=true",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeConfigMapName")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretName")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/configprops", this.port)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("contexts.sanitize.beans.[*].properties.sanitizeSecretNameTwo")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.config.sanitize_secrets;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalManagementPort;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class ConfigDataFabric8SanitizeEnvEndpointTests {
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoints.web.exposure.include=*" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class DefaultSettingsTest extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// management.endpoint.env.show-values = NEVER
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoint.env.show-values=NEVER" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class ExplicitNever extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = false
|
||||
*
|
||||
* Sanitizing functions must apply, but we have none registered, as such
|
||||
* everything is visible in plain text, both from configmaps and secrets.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize.yaml",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=false" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithoutSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// secret is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - management.endpoint.env.show-values = ALWAYS
|
||||
* - spring.cloud.kubernetes.sanitize.secrets = true
|
||||
*
|
||||
* Sanitizing functions must apply, and we have one registered, as such
|
||||
* configmap is visible in plain text, but secrets are sanitized.
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SanitizeApp.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "management.endpoints.web.exposure.include=*",
|
||||
"spring.config.import=kubernetes:,classpath:./sanitize-two.yaml",
|
||||
"management.endpoint.env.show-values=ALWAYS", "spring.cloud.kubernetes.sanitize.secrets=true" })
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
@Nested
|
||||
class AlwaysWithSanitizingFunction extends Fabric8SecretsSanitize {
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@LocalManagementPort
|
||||
private int port;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() {
|
||||
setUpBeforeClass(mockClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
// configmap is not sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeConfigMapName'].value")
|
||||
.isEqualTo("sanitizeConfigMapValue");
|
||||
|
||||
// first secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretName'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// second secret is sanitized
|
||||
webClient.get().uri("http://localhost:{port}/actuator/env", this.port).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("propertySources.[*].properties.['sanitize.sanitizeSecretNameTwo'].value")
|
||||
.isEqualTo(SanitizableData.SANITIZED_VALUE);
|
||||
|
||||
// secret is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/secret", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeSecretValue");
|
||||
|
||||
// configmap is usable from configuration properties
|
||||
webClient.get().uri("http://localhost:{port}/configmap", this.port).exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("$").isEqualTo("sanitizeConfigMapValue");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,14 +19,11 @@ package org.springframework.cloud.kubernetes.fabric8.config.secrets_property_sou
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.kubernetes.fabric8.config.example.App;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class,
|
||||
properties = { "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" })
|
||||
@TestPropertySource("classpath:/application-secrets.properties")
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user