diff --git a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/ElasticsearchReconciler.java b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/ElasticsearchReconciler.java index cc14e94d0ec0f41f039be14f8a6e92236199edfa..8bbd6d00c445b5c73e2e1d6e63cc9039bdc3fcc6 100644 --- a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/ElasticsearchReconciler.java +++ b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/ElasticsearchReconciler.java @@ -32,6 +32,7 @@ public class ElasticsearchReconciler implements Reconciler<OzgCloudElasticsearch service.createIndexIfMissing(namespace); service.createSecurityRoleIfMissing(namespace); service.createSecurityUserIfMissing(namespace, getPassword(secret)); + service.createCertificateIfMissing(namespace); log.info("Reconcile user successful."); return OzgCloudElasticsearchUpdateControlBuilder.fromResource(resource).withStatus(CustomResourceStatus.OK).build(); } catch (Exception e) { @@ -41,7 +42,7 @@ public class ElasticsearchReconciler implements Reconciler<OzgCloudElasticsearch } private String getPassword(Secret secret) { - return MapUtils.getString(secret.getStringData(), OzgCloudElasticsearchSecretHelper.SECRET_PASSWORD_FIELD); + return MapUtils.getString(secret.getStringData(), OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_PASSWORD_FIELD); } UpdateControl<OzgCloudElasticsearchCustomResource> buildExceptionUpdateControl(OzgCloudElasticsearchCustomResource resource, Exception e) { diff --git a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchProperties.java b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchProperties.java index a28b6fc83b226cff10ed2c3eded255fe63b297ed..f2434ecf59f586297b8e1f791f5f15b302b5e523 100644 --- a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchProperties.java +++ b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchProperties.java @@ -28,5 +28,7 @@ public class OzgCloudElasticsearchProperties { private String host; private int port; private String scheme; + private String certificateSecretName; + private String certificateSecretDataKey; } } \ No newline at end of file diff --git a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelper.java b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelper.java index 3528cbd21e392c78573cd69a454a938114338af5..eff6653a0c4f49d4df43a8057e212b143280c3bd 100644 --- a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelper.java +++ b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelper.java @@ -13,10 +13,13 @@ import lombok.RequiredArgsConstructor; public class OzgCloudElasticsearchSecretHelper { static final String SECRET_TYPE = "Opaque"; - static final String SECRET_ADDRESS_FIELD = "address"; - static final String SECRET_INDEX_FIELD = "index"; - static final String SECRET_PASSWORD_FIELD = "password"; - static final String SECRET_USERNAME_FIELD = "username"; + + static final String CREDENTIAL_SECRET_ADDRESS_FIELD = "address"; + static final String CREDENTIAL_SECRET_INDEX_FIELD = "index"; + static final String CREDENTIAL_SECRET_PASSWORD_FIELD = "password"; + static final String CREDENTIAL_SECRET_USERNAME_FIELD = "username"; + + static final String CERTIFICATE_SECRET_DATA_KEY = "ca.crt"; static final int PASSWORD_LENGTH = 15; @@ -26,10 +29,10 @@ public class OzgCloudElasticsearchSecretHelper { return new SecretBuilder() .withType(SECRET_TYPE) .withMetadata(createMetaData(name, namespace)) - .addToStringData(SECRET_ADDRESS_FIELD, buildSecretAddress()) - .addToStringData(SECRET_INDEX_FIELD, namespace) - .addToStringData(SECRET_PASSWORD_FIELD, generatePassword()) - .addToStringData(SECRET_USERNAME_FIELD, namespace) + .addToStringData(CREDENTIAL_SECRET_ADDRESS_FIELD, buildSecretAddress()) + .addToStringData(CREDENTIAL_SECRET_INDEX_FIELD, namespace) + .addToStringData(CREDENTIAL_SECRET_PASSWORD_FIELD, generatePassword()) + .addToStringData(CREDENTIAL_SECRET_USERNAME_FIELD, namespace) .build(); } @@ -40,7 +43,15 @@ public class OzgCloudElasticsearchSecretHelper { private String generatePassword() { return RandomStringUtils.randomAlphabetic(PASSWORD_LENGTH); } - + + public Secret buildCertificateSecret(String namespace, String data) { + return new SecretBuilder() + .withType(SECRET_TYPE) + .withMetadata(createMetaData(properties.getCertificateSecretName(), namespace)) + .addToStringData(CERTIFICATE_SECRET_DATA_KEY, data) + .build(); + } + private ObjectMeta createMetaData(String name, String namespace) { var metadata = new ObjectMeta(); metadata.setName(name); diff --git a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchService.java b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchService.java index 53b01dec058773cac5a5277bd98ed931bc17c459..ac6b827b95f36f7838629757f665cc2b88494525 100644 --- a/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchService.java +++ b/ozgcloud-elasticsearch-operator/src/main/java/de/ozgcloud/operator/OzgCloudElasticsearchService.java @@ -2,6 +2,7 @@ package de.ozgcloud.operator; import java.util.Objects; +import org.apache.commons.collections.MapUtils; import org.springframework.stereotype.Component; import de.ozgcloud.operator.PutRoleRequestData.IndicesPrivilegesData; @@ -37,7 +38,6 @@ public class OzgCloudElasticsearchService { createCredentialSecret(secretResource, namespace); log.info("Secret creation successful."); } - log.info("Secret already exists, return existing."); return secretResource.get(); } catch (Exception e) { log.info("Secret creation failed: " + e); @@ -52,11 +52,7 @@ public class OzgCloudElasticsearchService { private void createCredentialSecret(Resource<Secret> resource, String namespace) { createAdapter(resource).create(secretHelper.buildCredentialSecret(namespace, properties.getSecretCredentialsName())); } - - ResourceAdapter<Secret> createAdapter(Resource<Secret> resource) { - return new ResourceAdapter<Secret>(resource); - } - + // curl -k -X PUT -u elastic:$ELASTICSEARCH_PASSWORD -H 'Content-Type: application/json' 'https://ozg-search-cluster-es-http:9200/'$ES_NS_USER public void createIndexIfMissing(String name) throws Exception { log.info("Check index..."); @@ -121,4 +117,34 @@ public class OzgCloudElasticsearchService { } log.info("Delete index role successful."); } + + public void createCertificateIfMissing(String namespace) { + try { + log.info("Create certificate secret if missing..."); + var secretResource = kubernetesService.getSecretResource(namespace, properties.getCertificateSecretName()); + + if(Objects.isNull(secretResource.get())) { + log.info("create..."); + createCredentialSecret(namespace, secretResource); + log.info("create successful."); + } + } catch(Exception e) { + log.info("Certificate secret creation failed: " + e); + throw e; + } + } + + void createCredentialSecret(String namespace, Resource<Secret> secretResource) { + var serverSecretResource = kubernetesService.getSecretResource(properties.getServerProperties().getNamespace(), properties.getServerProperties().getCertificateSecretName()); + + createAdapter(secretResource).create(secretHelper.buildCertificateSecret(namespace, getSecretData(serverSecretResource.get()))); + } + + private String getSecretData(Secret secret) { + return MapUtils.getString(secret.getStringData(), properties.getServerProperties().getCertificateSecretDataKey()); + } + + ResourceAdapter<Secret> createAdapter(Resource<Secret> resource) { + return new ResourceAdapter<Secret>(resource); + } } \ No newline at end of file diff --git a/ozgcloud-elasticsearch-operator/src/main/resources/application.yml b/ozgcloud-elasticsearch-operator/src/main/resources/application.yml index 77f1e1bbaf65fcd66330a1df0b37160082b62a4d..b9c03f42f5dd9340efc3f0b0990342c4a02f1271 100644 --- a/ozgcloud-elasticsearch-operator/src/main/resources/application.yml +++ b/ozgcloud-elasticsearch-operator/src/main/resources/application.yml @@ -10,6 +10,7 @@ ozgcloud: port: 9200 scheme: https certificateSecretName: elasticsearch-certificate + certificateSecretDataKey: tlsCrt management: server: diff --git a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchReconcilerTest.java b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchReconcilerTest.java index 9c468aa55c4d298bed506d0a7d6f0ffaf1dce7e4..fc7c8ef1bca723311114d77e12a78782c485e1a7 100644 --- a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchReconcilerTest.java +++ b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchReconcilerTest.java @@ -37,7 +37,7 @@ class ElasticsearchReconcilerTest { private final OzgCloudElasticsearchCustomResource resource = ElasticsearchCustomResourceTestFactory.create(); private final static String PASSWORD = "dummyPassword"; - private final Secret secret = SecretTestFactory.createBuilder().addToStringData(OzgCloudElasticsearchSecretHelper.SECRET_PASSWORD_FIELD, PASSWORD).build(); + private final Secret secret = SecretTestFactory.createBuilder().addToStringData(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_PASSWORD_FIELD, PASSWORD).build(); @DisplayName("process flow") @Nested @@ -57,7 +57,7 @@ class ElasticsearchReconcilerTest { @SneakyThrows @Test - void shouldCheckIndex() { + void shouldCreateIndexIfMissing() { reconcile(); verify(service).createIndexIfMissing(NamespaceTestFactory.NAMESPACE); @@ -65,7 +65,7 @@ class ElasticsearchReconcilerTest { @SneakyThrows @Test - void shouldCheckSecurityRole() { + void shouldCreateecurityRoleIfMissing() { reconcile(); verify(service).createSecurityRoleIfMissing(NamespaceTestFactory.NAMESPACE); @@ -73,11 +73,19 @@ class ElasticsearchReconcilerTest { @SneakyThrows @Test - void shouldCheckSecurityUser() { + void shouldCallCreateSecurityUserIfMissing() { reconcile(); verify(service).createSecurityUserIfMissing(NamespaceTestFactory.NAMESPACE, PASSWORD); } + + @SneakyThrows + @Test + void shouldCallCreateCertificateIfMissing() { + reconcile(); + + verify(service).createCertificateIfMissing(NamespaceTestFactory.NAMESPACE); + } } @DisplayName("on exception") diff --git a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchSecretBuilderTest.java b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelperTest.java similarity index 62% rename from ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchSecretBuilderTest.java rename to ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelperTest.java index 227ff7a9c3960330b0ca7a5005b42e784b33179e..34e6ae631173cc9dda2f9f58a565523d368bcb7f 100644 --- a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/ElasticsearchSecretBuilderTest.java +++ b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchSecretHelperTest.java @@ -3,6 +3,8 @@ package de.ozgcloud.operator; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,7 +18,7 @@ import de.ozgcloud.operator.common.kubernetes.NamespaceTestFactory; import de.ozgcloud.operator.common.kubernetes.SecretTestFactory; import io.fabric8.kubernetes.api.model.Secret; -class ElasticsearchSecretBuilderTest { +class OzgCloudElasticsearchSecretHelperTest { @Spy @InjectMocks @@ -75,7 +77,7 @@ class ElasticsearchSecretBuilderTest { void shouldBeSet() { var secret = buildCredentialSecret(); - assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.SECRET_ADDRESS_FIELD, String.format("%s:%s", HOST, PORT)); + assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_ADDRESS_FIELD, String.format("%s:%s", HOST, PORT)); } @Test @@ -97,26 +99,73 @@ class ElasticsearchSecretBuilderTest { void shouldContainIndex() { var secret = buildCredentialSecret(); - assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.SECRET_INDEX_FIELD, NamespaceTestFactory.NAMESPACE); + assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_INDEX_FIELD, NamespaceTestFactory.NAMESPACE); } @Test void shouldContainPassword() { var secret = buildCredentialSecret(); - assertThat(secret.getStringData()).containsKey(OzgCloudElasticsearchSecretHelper.SECRET_PASSWORD_FIELD); - assertThat(secret.getStringData().get(OzgCloudElasticsearchSecretHelper.SECRET_PASSWORD_FIELD)).isNotNull(); + assertThat(secret.getStringData()).containsKey(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_PASSWORD_FIELD); + assertThat(secret.getStringData().get(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_PASSWORD_FIELD)).isNotNull(); } @Test void shouldContainUsername() { var secret = buildCredentialSecret(); - assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.SECRET_USERNAME_FIELD, NamespaceTestFactory.NAMESPACE); + assertThat(secret.getStringData()).containsEntry(OzgCloudElasticsearchSecretHelper.CREDENTIAL_SECRET_USERNAME_FIELD, NamespaceTestFactory.NAMESPACE); } private Secret buildCredentialSecret() { return builder.buildCredentialSecret(NamespaceTestFactory.NAMESPACE, SecretTestFactory.NAME); } } + + @DisplayName("Build certificate secret") + @Nested + class TestBuildCertificatSecret { + + private static final String DATA = "fgdrgsgreg"; + + @Test + void shouldHaveType() { + var secret = builder.buildCertificateSecret(NamespaceTestFactory.NAMESPACE, DATA); + + assertThat(secret.getType()).isEqualTo(OzgCloudElasticsearchSecretHelper.SECRET_TYPE); + } + + @DisplayName("metadata") + @Nested + class TestMetadata { + + private static final String CERTIFICATE_SECRET_NAME = "rfgsgrgsr"; + + @BeforeEach + void mock() { + when(properties.getCertificateSecretName()).thenReturn(CERTIFICATE_SECRET_NAME); + } + + @Test + void shouldContainName() { + var secret = builder.buildCertificateSecret(NamespaceTestFactory.NAMESPACE, DATA); + + assertThat(secret.getMetadata().getName()).isEqualTo(CERTIFICATE_SECRET_NAME); + } + + @Test + void shouldContainNamespace() { + var secret = builder.buildCertificateSecret(NamespaceTestFactory.NAMESPACE, DATA); + + assertThat(secret.getMetadata().getNamespace()).isEqualTo(NamespaceTestFactory.NAMESPACE); + } + } + + @Test + void shouldHaveCaCrtData() { + var secret = builder.buildCertificateSecret(NamespaceTestFactory.NAMESPACE, DATA); + + assertThat(secret.getStringData()).containsExactly(Map.entry(OzgCloudElasticsearchSecretHelper.CERTIFICATE_SECRET_DATA_KEY, DATA)); + } + } } diff --git a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchServiceTest.java b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchServiceTest.java index b560cba92af7cf74712fe6296dd82b6f76c6f6c5..953b78c2de93388d908b251d77d18143ffc0bf5c 100644 --- a/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchServiceTest.java +++ b/ozgcloud-elasticsearch-operator/src/test/java/de/ozgcloud/operator/OzgCloudElasticsearchServiceTest.java @@ -13,6 +13,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import de.ozgcloud.operator.OzgCloudElasticsearchProperties.OzgCloudElasticsearchServerProperties; import de.ozgcloud.operator.common.elasticsearch.ElasticsearchRemoteService; import de.ozgcloud.operator.common.kubernetes.KubernetesRemoteService; import de.ozgcloud.operator.common.kubernetes.NamespaceTestFactory; @@ -297,7 +298,7 @@ class OzgCloudElasticsearchServiceTest { @Nested class TestDeleteIndexIfExists { - private static final String INDEX_NAME = NamespaceTestFactory.NAMESPACE; + private static final String INDEX_NAME = NAMESPACE; @SneakyThrows @Test @@ -317,4 +318,99 @@ class OzgCloudElasticsearchServiceTest { verify(remoteService).deleteIndex(INDEX_NAME); } } + + @DisplayName("Create certificate if missing") + @Nested + class TestCreateCertificateIfMissing { + + private static final String CERTIFICATE_NAME = "dummySecretName"; + + @Mock + private Resource<Secret> secretResource; + + @DisplayName("process flow") + @Nested + class TestProcessFlow { + + @Mock + private OzgCloudElasticsearchServerProperties serverProperties; + + @BeforeEach + void mock() { + when(kubernetesService.getSecretResource(any(), any())).thenReturn(secretResource); + when(properties.getCertificateSecretName()).thenReturn(CERTIFICATE_NAME); + } + + @Test + void shouldGetCertificateSecret() { + when(secretResource.get()).thenReturn(SecretTestFactory.create()); + + service.createCertificateIfMissing(NAMESPACE); + + verify(kubernetesService).getSecretResource(NAMESPACE, CERTIFICATE_NAME); + } + + @Test + void shouldCreateIfMissing() { + when(secretResource.get()).thenReturn(null); + doNothing().when(service).createCredentialSecret(any(), any()); + + service.createCertificateIfMissing(NAMESPACE); + + verify(service).createCredentialSecret(NAMESPACE, secretResource); + } + } + + @DisplayName("create credential secret") + @Nested + class TestCreateCredentialSecret { + + private static final String SERVER_NAMESPACE = ""; + private static final String SERVER_CERTIFICATE_SECRET_NAME = ""; + + private static final String SERVER_CERTIFICATE_SECRET_DATA_KEY = "fesfdsfd"; + private static final String SERVER_CERTIFICATE_SECRET_DATA = "dsadwadas"; + private static final Secret SERVER_CERTIFICATE_SECRET = SecretTestFactory.createBuilder() + .addToStringData(SERVER_CERTIFICATE_SECRET_DATA_KEY, SERVER_CERTIFICATE_SECRET_DATA) + .build(); + private static final Secret CREDENTIAL_SECRET = SecretTestFactory.create(); + @Mock + private OzgCloudElasticsearchServerProperties serverProperties; + @Mock + private ResourceAdapter<Secret> resourceAdapter; + + @BeforeEach + void mock() { + when(properties.getServerProperties()).thenReturn(serverProperties); + when(serverProperties.getNamespace()).thenReturn(SERVER_NAMESPACE); + when(serverProperties.getCertificateSecretName()).thenReturn(SERVER_CERTIFICATE_SECRET_NAME); + when(serverProperties.getCertificateSecretDataKey()).thenReturn(SERVER_CERTIFICATE_SECRET_DATA_KEY); + when(kubernetesService.getSecretResource(any(), any())).thenReturn(secretResource); + when(secretResource.get()).thenReturn(SERVER_CERTIFICATE_SECRET); + doReturn(resourceAdapter).when(service).createAdapter(any()); + when(secretHelper.buildCertificateSecret(any(), any())).thenReturn(CREDENTIAL_SECRET); + } + + @Test + void shouldGetServerSecret() { + service.createCredentialSecret(NAMESPACE, secretResource); + + verify(kubernetesService).getSecretResource(SERVER_NAMESPACE, SERVER_CERTIFICATE_SECRET_NAME); + } + + @Test + void shouldBuildSecret() { + service.createCredentialSecret(NAMESPACE, secretResource); + + verify(secretHelper).buildCertificateSecret(NAMESPACE, SERVER_CERTIFICATE_SECRET_DATA); + } + + @Test + void shouldCreate() { + service.createCredentialSecret(NAMESPACE, secretResource); + + verify(resourceAdapter).create(CREDENTIAL_SECRET); + } + } + } } \ No newline at end of file