From ef4a43c9e06056161a61271726fe8d2cbca6d116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Thu, 22 Jun 2023 18:27:18 +0200 Subject: [PATCH 01/28] HDDS-8591 Create polling mechanism for root ca mechanism --- .../apache/hadoop/hdds/HddsConfigKeys.java | 4 + .../hadoop/hdds/security/SecurityConfig.java | 14 ++ .../src/main/resources/ozone-default.xml | 22 +++- .../client/RootCaRotationPoller.java | 110 ++++++++++++++++ .../utils/TestRootCaRotationPoller.java | 122 ++++++++++++++++++ 5 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java create mode 100644 hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java index 1cd0a6a47391..d3179be00853 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java @@ -216,6 +216,10 @@ public final class HddsConfigKeys { // format hh:mm:ss, representing hour, minute, and second public static final String HDDS_X509_CA_ROTATION_TIME_OF_DAY_DEFAULT = "02:00:00"; + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY = + "hdds.x509.rootca.client.polling.frequency"; + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT + = "PT2h"; public static final String HDDS_CONTAINER_REPLICATION_COMPRESSION = "hdds.container.replication.compression"; diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index 543d59348c3e..35fa67e5d9d2 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -50,6 +50,8 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_CA_ROTATION_TIME_OF_DAY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PUBLIC_KEY_FILE; @@ -128,6 +130,7 @@ public class SecurityConfig { private final Pattern caRotationTimeOfDayPattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); private final SslProvider grpcSSLProvider; + private final Duration rootCaClientPollingFrequency; /** * Constructs a SecurityConfig. @@ -220,6 +223,13 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); + String rootCaClientPollingFrequencyString = configuration.get( + HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, + HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT); + + this.rootCaClientPollingFrequency = + Duration.parse(rootCaClientPollingFrequencyString); + this.externalRootCaCert = configuration.get( HDDS_X509_ROOTCA_CERTIFICATE_FILE, HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT); @@ -508,6 +518,10 @@ public String getCaRotationTimeOfDay() { return caRotationTimeOfDay; } + public Duration getRootCaClientPollingFrequency() { + return rootCaClientPollingFrequency; + } + /** * Return true if using test certificates with authority as localhost. This * should be used only for unit test where certificates are generated by diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 47c32219c605..709b46816cb5 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2247,11 +2247,29 @@ hdds.x509.ca.rotation.time-of-day 02:00:00 OZONE, HDDS, SECURITY - Time of day to start the rotation. Default 02:00 AM to avoid impacting - daily workload. The supported format is 'hh:mm:ss', representing hour, minute, + Time of day to start the rotation. Default 02:00 AM to avoid + impacting + daily workload. The supported format is 'hh:mm:ss', representing hour, + minute, and second. + + ozone.scm.security.service.address + + OZONE, HDDS, SECURITY + + + hdds.x509.rootca.client.polling.frequency + PT2h + Frequency to use for polling in certificate clients for a new + root ca certificate. Every time the specified time duration elapses, + the clients send a request to the SCMs to see if a new root ca + certificate was generated. Once there is a change, the system + automatically adds the new root ca to the clients' + trust stores and requests a new certificate to be signed. + + ozone.scm.security.handler.count.key 2 diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java new file mode 100644 index 000000000000..263f1de7d458 --- /dev/null +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + *

+ * http://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.apache.hadoop.hdds.security.x509.certificate.client; + +import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; +import org.apache.hadoop.hdds.security.SecurityConfig; +import org.apache.hadoop.ozone.OzoneSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Poller mechanism for Root Ca Rotation for clients. + */ +public class RootCaRotationPoller implements Runnable, Closeable { + + private static final Logger LOG = + LoggerFactory.getLogger(RootCaRotationPoller.class); + private final List>> rootCaListConsumers; + private final ScheduledExecutorService poller; + private final Duration pollingRate; + private Set knownRootCerts; + private final SCMSecurityProtocolClientSideTranslatorPB scmSecureClient; + + public RootCaRotationPoller(SecurityConfig securityConfig, + Set initiallyKnownRootCaCerts, + SCMSecurityProtocolClientSideTranslatorPB scmSecureClient) { + this.scmSecureClient = scmSecureClient; + this.knownRootCerts = initiallyKnownRootCaCerts; + poller = Executors.newSingleThreadScheduledExecutor(); + pollingRate = securityConfig.getRootCaClientPollingFrequency(); + rootCaListConsumers = new ArrayList<>(); + } + + private void pollRootCas() { + try { + List pemEncodedRootCaList = + scmSecureClient.getAllRootCaCertificates(); + List scmRootCaCerts = + OzoneSecurityUtil.convertToX509(pemEncodedRootCaList); + if (!knownRootCerts.containsAll(scmRootCaCerts)) { + rootCaListConsumers.forEach(c -> c.accept(scmRootCaCerts)); + knownRootCerts = new HashSet<>(); + knownRootCerts.addAll(scmRootCaCerts); + } + } catch (IOException e) { + LOG.error("Error while trying to rotate root ca certificate", e); + } + } + + public void addRootCaRotationConsumer( + Consumer> consumer) { + rootCaListConsumers.add(consumer); + } + + @Override + public void run() { + poller.scheduleAtFixedRate(this::pollRootCas, 0, + pollingRate.getSeconds(), TimeUnit.SECONDS); + } + + @Override + public void close() { + executorServiceShutdownGraceful(poller); + } + + private void executorServiceShutdownGraceful(ExecutorService executor) { + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + LOG.error("Unable to shutdown state machine properly."); + } + } catch (InterruptedException e) { + LOG.error("Error attempting to shutdown.", e); + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java new file mode 100644 index 000000000000..707e77e07fb4 --- /dev/null +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.hadoop.hdds.security.x509.certificate.utils; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; +import org.apache.hadoop.hdds.security.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.client.RootCaRotationPoller; +import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.apache.ozone.test.GenericTestUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; + +/** + * Test for Root Ca Rotation polling mechanism on client side. + */ +public class TestRootCaRotationPoller { + + private SecurityConfig secConf; + + @Mock + private SCMSecurityProtocolClientSideTranslatorPB scmSecurityClient; + @Mock + private Consumer> consumer; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + OzoneConfiguration conf = new OzoneConfiguration(); + conf.set(HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, "PT1s"); + secConf = new SecurityConfig(conf); + } + + @Test + public void testPollerDoesNotInvokeConsumersPrematurely() throws IOException { + RootCaRotationPoller poller = new RootCaRotationPoller(secConf, + new HashSet<>(), scmSecurityClient); + + Mockito.when(scmSecurityClient.getAllRootCaCertificates()) + .thenReturn(new ArrayList<>()); + AtomicBoolean atomicBoolean = new AtomicBoolean(); + atomicBoolean.set(false); + poller.addRootCaRotationConsumer(certificates -> atomicBoolean.set(true)); + poller.run(); + Assertions.assertThrows(TimeoutException.class, () -> + GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000)); + } + + @Test + public void testPollerInvokesConsumers() throws Exception { + X509Certificate knownCert = generateX509Cert(new OzoneConfiguration(), + LocalDateTime.now(), Duration.ofSeconds(50)); + X509Certificate newRootCa = generateX509Cert(new OzoneConfiguration(), + LocalDateTime.now(), Duration.ofSeconds(50)); + HashSet knownCerts = new HashSet<>(); + knownCerts.add(knownCert); + List certsFromScm = new ArrayList<>(); + certsFromScm.add(CertificateCodec.getPEMEncodedString(knownCert)); + certsFromScm.add(CertificateCodec.getPEMEncodedString(newRootCa)); + RootCaRotationPoller poller = new RootCaRotationPoller(secConf, + knownCerts, scmSecurityClient); + poller.run(); + Mockito.when(scmSecurityClient.getAllRootCaCertificates()) + .thenReturn(certsFromScm); + AtomicBoolean atomicBoolean = new AtomicBoolean(); + atomicBoolean.set(false); + poller.addRootCaRotationConsumer( + certificates -> { + atomicBoolean.set(true); + Assertions.assertEquals(certificates.size(), 2); + }); + GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000); + } + + private X509Certificate generateX509Cert( + OzoneConfiguration conf, LocalDateTime startDate, + Duration certLifetime) throws Exception { + KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); + LocalDateTime start = startDate == null ? LocalDateTime.now() : startDate; + LocalDateTime end = start.plus(certLifetime); + return new JcaX509CertificateConverter().getCertificate( + SelfSignedCertificate.newBuilder().setBeginDate(start) + .setEndDate(end).setClusterID("cluster").setKey(keyPair) + .setSubject("localhost").setConfiguration(secConf).setScmID("test") + .build()); + } +} From ff39ecf2c6d0f889c51a978d533885027fd3ba3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Mon, 26 Jun 2023 09:24:02 +0200 Subject: [PATCH 02/28] HDDS-8591 Fix review comments, add logging --- .../src/main/resources/ozone-default.xml | 5 --- .../client/RootCaRotationPoller.java | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 709b46816cb5..4028684092a9 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2254,11 +2254,6 @@ and second. - - ozone.scm.security.service.address - - OZONE, HDDS, SECURITY - hdds.x509.rootca.client.polling.frequency PT2h diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index 263f1de7d458..5015a6859ef0 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hdds.security.x509.certificate.client; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; import org.apache.hadoop.ozone.OzoneSecurityUtil; @@ -25,9 +26,11 @@ import java.io.Closeable; import java.io.IOException; +import java.math.BigInteger; import java.security.cert.X509Certificate; import java.time.Duration; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,6 +39,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Poller mechanism for Root Ca Rotation for clients. @@ -64,13 +68,22 @@ private void pollRootCas() { try { List pemEncodedRootCaList = scmSecureClient.getAllRootCaCertificates(); - List scmRootCaCerts = + List rootCAsFromSCM = OzoneSecurityUtil.convertToX509(pemEncodedRootCaList); - if (!knownRootCerts.containsAll(scmRootCaCerts)) { - rootCaListConsumers.forEach(c -> c.accept(scmRootCaCerts)); - knownRootCerts = new HashSet<>(); - knownRootCerts.addAll(scmRootCaCerts); + List scmCertsWithoutKnownCerts + = new ArrayList<>(rootCAsFromSCM); + scmCertsWithoutKnownCerts.removeAll(knownRootCerts); + if (scmCertsWithoutKnownCerts.isEmpty()) { + return; } + LOG.info("Some root CAs are not known to the client out of the root " + + "CAs known to the SCMs. Root CA Cert ids known to the client: " + + getPrintableCertIds(knownRootCerts) + ". Root CA Cert ids from " + + "SCM not known by the client: " + + getPrintableCertIds(scmCertsWithoutKnownCerts)); + rootCaListConsumers.forEach(c -> c.accept(rootCAsFromSCM)); + knownRootCerts = new HashSet<>(rootCAsFromSCM); + } catch (IOException e) { LOG.error("Error while trying to rotate root ca certificate", e); } @@ -99,7 +112,7 @@ private void executorServiceShutdownGraceful(ExecutorService executor) { executor.shutdownNow(); } if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - LOG.error("Unable to shutdown state machine properly."); + LOG.error("Unable to shutdown root ca certificate rotation poller."); } } catch (InterruptedException e) { LOG.error("Error attempting to shutdown.", e); @@ -107,4 +120,11 @@ private void executorServiceShutdownGraceful(ExecutorService executor) { Thread.currentThread().interrupt(); } } + + private String getPrintableCertIds(Collection certs) { + return StringUtils.join(certs.stream() + .map(X509Certificate::getSerialNumber) + .map(BigInteger::toString) + .collect(Collectors.toList()), ", "); + } } From 33a40d93e92a1e850f4ceb33fda3377a09e8488f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Tue, 27 Jun 2023 18:16:46 +0200 Subject: [PATCH 03/28] HDDS-8591 Switch to completable future --- .../client/RootCaRotationPoller.java | 26 ++++++++++++----- .../utils/TestRootCaRotationPoller.java | 29 ++++++++++--------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index 5015a6859ef0..af06291aef5e 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -34,11 +34,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -48,7 +49,8 @@ public class RootCaRotationPoller implements Runnable, Closeable { private static final Logger LOG = LoggerFactory.getLogger(RootCaRotationPoller.class); - private final List>> rootCaListConsumers; + private final List, CompletableFuture>> + rootCARotationProcessors; private final ScheduledExecutorService poller; private final Duration pollingRate; private Set knownRootCerts; @@ -61,7 +63,7 @@ public RootCaRotationPoller(SecurityConfig securityConfig, this.knownRootCerts = initiallyKnownRootCaCerts; poller = Executors.newSingleThreadScheduledExecutor(); pollingRate = securityConfig.getRootCaClientPollingFrequency(); - rootCaListConsumers = new ArrayList<>(); + rootCARotationProcessors = new ArrayList<>(); } private void pollRootCas() { @@ -81,17 +83,25 @@ private void pollRootCas() { getPrintableCertIds(knownRootCerts) + ". Root CA Cert ids from " + "SCM not known by the client: " + getPrintableCertIds(scmCertsWithoutKnownCerts)); - rootCaListConsumers.forEach(c -> c.accept(rootCAsFromSCM)); - knownRootCerts = new HashSet<>(rootCAsFromSCM); + CompletableFuture allRootCAProcessorFutures = + CompletableFuture.allOf(rootCARotationProcessors.stream() + .map(c -> c.apply(rootCAsFromSCM)) + .toArray(CompletableFuture[]::new)); + + allRootCAProcessorFutures.whenComplete((unused, throwable) -> { + if (throwable == null) { + knownRootCerts = new HashSet<>(rootCAsFromSCM); + } + }); } catch (IOException e) { LOG.error("Error while trying to rotate root ca certificate", e); } } - public void addRootCaRotationConsumer( - Consumer> consumer) { - rootCaListConsumers.add(consumer); + public void addRootCARotationProcessor( + Function, CompletableFuture> processor) { + rootCARotationProcessors.add(processor); } @Override diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java index 707e77e07fb4..461b1c4cc42a 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -40,9 +40,9 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; @@ -55,8 +55,6 @@ public class TestRootCaRotationPoller { @Mock private SCMSecurityProtocolClientSideTranslatorPB scmSecurityClient; - @Mock - private Consumer> consumer; @BeforeEach public void setup() { @@ -67,7 +65,7 @@ public void setup() { } @Test - public void testPollerDoesNotInvokeConsumersPrematurely() throws IOException { + public void testPollerDoesNotInvokeRootCaProcessor() throws IOException { RootCaRotationPoller poller = new RootCaRotationPoller(secConf, new HashSet<>(), scmSecurityClient); @@ -75,17 +73,22 @@ public void testPollerDoesNotInvokeConsumersPrematurely() throws IOException { .thenReturn(new ArrayList<>()); AtomicBoolean atomicBoolean = new AtomicBoolean(); atomicBoolean.set(false); - poller.addRootCaRotationConsumer(certificates -> atomicBoolean.set(true)); + poller.addRootCARotationProcessor( + certificates -> CompletableFuture.supplyAsync(() -> { + atomicBoolean.set(true); + Assertions.assertEquals(certificates.size(), 2); + return null; + })); poller.run(); Assertions.assertThrows(TimeoutException.class, () -> GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000)); } @Test - public void testPollerInvokesConsumers() throws Exception { - X509Certificate knownCert = generateX509Cert(new OzoneConfiguration(), + public void testPollerInvokesRootCaProcessors() throws Exception { + X509Certificate knownCert = generateX509Cert( LocalDateTime.now(), Duration.ofSeconds(50)); - X509Certificate newRootCa = generateX509Cert(new OzoneConfiguration(), + X509Certificate newRootCa = generateX509Cert( LocalDateTime.now(), Duration.ofSeconds(50)); HashSet knownCerts = new HashSet<>(); knownCerts.add(knownCert); @@ -99,17 +102,17 @@ public void testPollerInvokesConsumers() throws Exception { .thenReturn(certsFromScm); AtomicBoolean atomicBoolean = new AtomicBoolean(); atomicBoolean.set(false); - poller.addRootCaRotationConsumer( - certificates -> { + poller.addRootCARotationProcessor( + certificates -> CompletableFuture.supplyAsync(() -> { atomicBoolean.set(true); Assertions.assertEquals(certificates.size(), 2); - }); + return null; + })); GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000); } private X509Certificate generateX509Cert( - OzoneConfiguration conf, LocalDateTime startDate, - Duration certLifetime) throws Exception { + LocalDateTime startDate, Duration certLifetime) throws Exception { KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); LocalDateTime start = startDate == null ? LocalDateTime.now() : startDate; LocalDateTime end = start.plus(certLifetime); From 265be729eda3a65932e24fda9057bc1f1c4fedbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Tue, 27 Jun 2023 18:19:50 +0200 Subject: [PATCH 04/28] HDDS-8591 Fix whitespace change --- hadoop-hdds/common/src/main/resources/ozone-default.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 4028684092a9..1914737bb7f6 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2247,10 +2247,8 @@ hdds.x509.ca.rotation.time-of-day 02:00:00 OZONE, HDDS, SECURITY - Time of day to start the rotation. Default 02:00 AM to avoid - impacting - daily workload. The supported format is 'hh:mm:ss', representing hour, - minute, + Time of day to start the rotation. Default 02:00 AM to avoid impacting + daily workload. The supported format is 'hh:mm:ss', representing hour, minute, and second. From 5434206c9220d16398ab27e89f533d95dcf64b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 08:42:11 +0200 Subject: [PATCH 05/28] HDDS-8592 Rename CertificateLifeTimeMonitor --- .../x509/certificate/client/DefaultCertificateClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index d64cabf5c92a..31aa88af5071 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1238,7 +1238,7 @@ public synchronized void startCertificateMonitor() { .setDaemon(true).build()); } this.executorService.scheduleAtFixedRate( - new CertificateLifetimeMonitor(this), + new CertificateRenewerService(this), timeBeforeGracePeriod, interval, TimeUnit.MILLISECONDS); getLogger().info("CertificateLifetimeMonitor for {} is started with " + "first delay {} ms and interval {} ms.", component, @@ -1248,10 +1248,10 @@ public synchronized void startCertificateMonitor() { /** * Task to monitor certificate lifetime and renew the certificate if needed. */ - public class CertificateLifetimeMonitor implements Runnable { + public class CertificateRenewerService implements Runnable { private CertificateClient certClient; - public CertificateLifetimeMonitor(CertificateClient client) { + public CertificateRenewerService(CertificateClient client) { this.certClient = client; } From c0260011f18d032ca645b36dec2a394006d691b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 08:42:32 +0200 Subject: [PATCH 06/28] HDDS-8592 Rename CertificateLifeTimeMonitor --- .../x509/certificate/client/DefaultCertificateClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 31aa88af5071..f01ba37e4940 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1246,7 +1246,7 @@ public synchronized void startCertificateMonitor() { } /** - * Task to monitor certificate lifetime and renew the certificate if needed. + * Task to monitor certificate lifetime and renew the certificate if needed. */ public class CertificateRenewerService implements Runnable { private CertificateClient certClient; From 657c50c2d263b22cf9e58bcad32a7a6275940555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 14:59:59 +0200 Subject: [PATCH 07/28] HDDS-8592 Refactor DefaultCertificateClient to have signAndStoreCertificate in only one place. --- .../client/DNCertificateClient.java | 48 ++------------ .../client/DefaultCertificateClient.java | 54 ++++++++++++++-- .../client/SCMCertificateClient.java | 10 ++- .../client/TestDefaultCertificateClient.java | 24 +++++-- .../ozone/security/OMCertificateClient.java | 45 ++----------- .../security/ReconCertificateClient.java | 64 +++++-------------- 6 files changed, 101 insertions(+), 144 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java index 8c5c91320338..27b2da575873 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java @@ -20,11 +20,9 @@ package org.apache.hadoop.hdds.security.x509.certificate.client; import org.apache.hadoop.hdds.protocol.DatanodeDetails; -import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; +import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; -import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; -import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; import org.apache.hadoop.security.UserGroupInformation; @@ -34,7 +32,6 @@ import java.io.IOException; import java.net.InetAddress; -import java.nio.file.Path; import java.security.KeyPair; import java.util.function.Consumer; @@ -98,45 +95,10 @@ public CertificateSignRequest.Builder getCSRBuilder() } @Override - public String signAndStoreCertificate(PKCS10CertificationRequest csr, - Path certificatePath) throws CertificateException { - try { - // TODO: For SCM CA we should fetch certificate from multiple SCMs. - SCMSecurityProtocolProtos.SCMGetCertResponseProto response = - getScmSecureClient().getDataNodeCertificateChain( - dn.getProtoBufMessage(), getEncodedString(csr)); - - // Persist certificates. - if (response.hasX509CACertificate()) { - String pemEncodedCert = response.getX509Certificate(); - CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certificatePath); - // Certs will be added to cert map after reloadAllCertificate called - storeCertificate(pemEncodedCert, CAType.NONE, - certCodec, - false); - storeCertificate(response.getX509CACertificate(), - CAType.SUBORDINATE, - certCodec, false); - - // Store Root CA certificate. - if (response.hasX509RootCACertificate()) { - storeCertificate(response.getX509RootCACertificate(), - CAType.ROOT, certCodec, false); - } - // Return the default certificate ID - return CertificateCodec.getX509Certificate(pemEncodedCert) - .getSerialNumber() - .toString(); - } else { - throw new CertificateException("Unable to retrieve datanode " + - "certificate chain."); - } - } catch (IOException | java.security.cert.CertificateException e) { - LOG.error("Error while signing and storing SCM signed certificate.", e); - throw new CertificateException( - "Error while signing and storing SCM signed certificate.", e); - } + public SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest csr) throws IOException { + return getScmSecureClient().getDataNodeCertificateChain( + dn.getProtoBufMessage(), getEncodedString(csr)); } @Override diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index f01ba37e4940..a5afcc124cfb 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -78,6 +78,7 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_BACKUP_KEY_CERT_DIR_NAME_SUFFIX; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_NEW_KEY_CERT_DIR_NAME_SUFFIX; +import static org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import static org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse.FAILURE; import static org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse.GETCERT; import static org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse.REINIT; @@ -1205,14 +1206,53 @@ private synchronized String updateCertSerialId(String newCertSerialId) { return certSerialId; } - protected abstract String signAndStoreCertificate( - PKCS10CertificationRequest request, Path certificatePath) - throws CertificateException; + protected abstract SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) throws IOException; - public String signAndStoreCertificate( - PKCS10CertificationRequest request) throws CertificateException { - return updateCertSerialId(signAndStoreCertificate(request, - securityConfig.getCertificateLocation(getComponentName()))); + public String signAndStoreCertificate(PKCS10CertificationRequest request) + throws CertificateException { + return signAndStoreCertificate(request, + securityConfig.getCertificateLocation(getComponentName())); + } + + public String signAndStoreCertificate(PKCS10CertificationRequest request, + Path certificatePath) throws CertificateException { + try { + SCMGetCertResponseProto response = + getCertificateSignResponse(request); + + if (response.hasX509CACertificate()) { + String pemEncodedCert = response.getX509Certificate(); + CertificateCodec certCodec = new CertificateCodec( + getSecurityConfig(), certificatePath); + // Certs will be added to cert map after reloadAllCertificate called + storeCertificate(pemEncodedCert, CAType.NONE, + certCodec, + false); + storeCertificate(response.getX509CACertificate(), + CAType.SUBORDINATE, + certCodec, false); + + // Store Root CA certificate. + if (response.hasX509RootCACertificate()) { + storeCertificate(response.getX509RootCACertificate(), + CAType.ROOT, certCodec, false); + } + // Return the default certificate ID + return updateCertSerialId( + CertificateCodec.getX509Certificate(pemEncodedCert) + .getSerialNumber() + .toString()); + } else { + throw new CertificateException("Unable to retrieve datanode " + + "certificate chain."); + } + } catch (IOException | java.security.cert.CertificateException e) { + logger.error("Error while signing and storing SCM signed certificate.", + e); + throw new CertificateException( + "Error while signing and storing SCM signed certificate.", e); + } } public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java index 51eb2959a6e9..9cbf51582c70 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hdds.security.x509.certificate.client; +import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; @@ -149,9 +150,16 @@ public Logger getLogger() { return LOG; } + @Override + protected SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) { + throw new UnsupportedOperationException("getCertSignResponse of " + + " SCMCertificateClient is not supported currently"); + } + @Override public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certPath) throws CertificateException { + Path certPath) { throw new UnsupportedOperationException("signAndStoreCertificate of " + " SCMCertificateClient is not supported currently"); } diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java index 2fe0bf84ea24..b20b6c3180a4 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java @@ -20,7 +20,7 @@ import org.apache.hadoop.hdds.HddsConfigKeys; import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; -import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; +import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse; @@ -483,6 +483,12 @@ public X509Certificate getCertificate() { return mockCert; } + @Override + protected SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) { + return null; + } + @Override public String signAndStoreCertificate( PKCS10CertificationRequest request, Path certificatePath) { @@ -529,10 +535,10 @@ public void testRenewAndStoreKeyAndCertificate() throws Exception { X509Certificate newCert = generateX509Cert(null); String pemCert = CertificateCodec.getPEMEncodedString(newCert); - SCMSecurityProtocolProtos.SCMGetCertResponseProto responseProto = - SCMSecurityProtocolProtos.SCMGetCertResponseProto - .newBuilder().setResponseCode(SCMSecurityProtocolProtos - .SCMGetCertResponseProto.ResponseCode.success) + SCMGetCertResponseProto responseProto = + SCMGetCertResponseProto + .newBuilder() + .setResponseCode(SCMGetCertResponseProto.ResponseCode.success) .setX509Certificate(pemCert) .setX509CACertificate(pemCert) .build(); @@ -619,7 +625,13 @@ public void testCloseCertificateClient(@TempDir File metaDir) ) { @Override - protected String signAndStoreCertificate( + protected SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) { + return null; + } + + @Override + public String signAndStoreCertificate( PKCS10CertificationRequest request, Path certificatePath) { return ""; } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java index 88312cacf403..997b7939cca5 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java @@ -24,9 +24,7 @@ import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; -import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CommonCertificateClient; -import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; import org.apache.hadoop.ozone.om.OMStorage; @@ -36,7 +34,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.nio.file.Path; import java.security.KeyPair; import java.util.function.Consumer; @@ -121,44 +118,14 @@ public CertificateSignRequest.Builder getCSRBuilder() } @Override - public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certificatePath) throws CertificateException { - try { - SCMGetCertResponseProto response = getScmSecureClient() - .getOMCertChain(omInfo, getEncodedString(request)); - - String pemEncodedCert = response.getX509Certificate(); - CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certificatePath); - - // Store SCM CA certificate. - if (response.hasX509CACertificate()) { - String pemEncodedRootCert = response.getX509CACertificate(); - storeCertificate(pemEncodedRootCert, - CAType.SUBORDINATE, certCodec, false); - storeCertificate(pemEncodedCert, CAType.NONE, certCodec, - false); - - // Store Root CA certificate if available. - if (response.hasX509RootCACertificate()) { - storeCertificate(response.getX509RootCACertificate(), - CAType.ROOT, certCodec, false); - } - return CertificateCodec.getX509Certificate(pemEncodedCert) - .getSerialNumber().toString(); - } else { - throw new CertificateException("Unable to retrieve OM certificate " + - "chain."); - } - } catch (IOException | java.security.cert.CertificateException e) { - LOG.error("Error while signing and storing SCM signed certificate.", e); - throw new CertificateException( - "Error while signing and storing SCM signed certificate.", e); - } + public Logger getLogger() { + return LOG; } @Override - public Logger getLogger() { - return LOG; + protected SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) throws IOException { + return getScmSecureClient().getOMCertChain( + omInfo, getEncodedString(request)); } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/security/ReconCertificateClient.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/security/ReconCertificateClient.java index 2a78ddae23b3..6dd9afc8944a 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/security/ReconCertificateClient.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/security/ReconCertificateClient.java @@ -18,12 +18,10 @@ package org.apache.hadoop.ozone.recon.security; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; -import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; +import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; -import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CommonCertificateClient; -import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; import org.apache.hadoop.ozone.recon.scm.ReconStorageConfig; @@ -34,11 +32,9 @@ import java.io.IOException; import java.net.InetAddress; -import java.nio.file.Path; import java.security.KeyPair; import java.util.function.Consumer; -import static org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec.getX509Certificate; import static org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest.getEncodedString; import static org.apache.hadoop.hdds.security.x509.exception.CertificateException.ErrorCode.CSR_ERROR; @@ -89,51 +85,23 @@ public CertificateSignRequest.Builder getCSRBuilder() } @Override - public String signAndStoreCertificate(PKCS10CertificationRequest csr, - Path certificatePath) throws CertificateException { - try { - SCMSecurityProtocolProtos.SCMGetCertResponseProto response; - HddsProtos.NodeDetailsProto.Builder reconDetailsProtoBuilder = - HddsProtos.NodeDetailsProto.newBuilder() - .setHostName(InetAddress.getLocalHost().getHostName()) - .setClusterId(clusterID) - .setUuid(reconID) - .setNodeType(HddsProtos.NodeType.RECON); - // TODO: For SCM CA we should fetch certificate from multiple SCMs. - response = getScmSecureClient().getCertificateChain( - reconDetailsProtoBuilder.build(), getEncodedString(csr)); - - // Persist certificates. - if (response.hasX509CACertificate()) { - String pemEncodedCert = response.getX509Certificate(); - CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certificatePath); - storeCertificate(pemEncodedCert, CAType.NONE, - certCodec, - false); - storeCertificate(response.getX509CACertificate(), - CAType.SUBORDINATE, - certCodec, false); - - // Store Root CA certificate. - if (response.hasX509RootCACertificate()) { - storeCertificate(response.getX509RootCACertificate(), - CAType.ROOT, certCodec, false); - } - return getX509Certificate(pemEncodedCert).getSerialNumber().toString(); - } else { - throw new CertificateException("Unable to retrieve recon certificate " + - "chain"); - } - } catch (IOException | java.security.cert.CertificateException e) { - LOG.error("Error while signing and storing SCM signed certificate.", e); - throw new CertificateException( - "Error while signing and storing SCM signed certificate.", e); - } + public Logger getLogger() { + return LOG; } @Override - public Logger getLogger() { - return LOG; + protected SCMGetCertResponseProto getCertificateSignResponse( + PKCS10CertificationRequest request) throws IOException { + SCMGetCertResponseProto response; + HddsProtos.NodeDetailsProto.Builder reconDetailsProtoBuilder = + HddsProtos.NodeDetailsProto.newBuilder() + .setHostName(InetAddress.getLocalHost().getHostName()) + .setClusterId(clusterID) + .setUuid(reconID) + .setNodeType(HddsProtos.NodeType.RECON); + // TODO: For SCM CA we should fetch certificate from multiple SCMs. + response = getScmSecureClient().getCertificateChain( + reconDetailsProtoBuilder.build(), getEncodedString(request)); + return response; } } From aa00ff64ca4c839aeca3af153c64e39f56a98bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 15:00:59 +0200 Subject: [PATCH 08/28] HDDS-8592 Remove warning from non thrown exception --- .../x509/certificate/client/DefaultCertificateClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index a5afcc124cfb..2e00a0314629 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1255,8 +1255,7 @@ public String signAndStoreCertificate(PKCS10CertificationRequest request, } } - public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() - throws IOException { + public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } From 833484d6b9b7178a05132ba742ba9ab5c5add610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 15:24:16 +0200 Subject: [PATCH 09/28] HDDS-8592 Fetch root CAs from the scm and store them --- .../client/DefaultCertificateClient.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 2e00a0314629..74e715005615 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1233,16 +1233,11 @@ public String signAndStoreCertificate(PKCS10CertificationRequest request, CAType.SUBORDINATE, certCodec, false); - // Store Root CA certificate. - if (response.hasX509RootCACertificate()) { - storeCertificate(response.getX509RootCACertificate(), - CAType.ROOT, certCodec, false); - } + getAndStoreAllRootCAs(certCodec); // Return the default certificate ID return updateCertSerialId( CertificateCodec.getX509Certificate(pemEncodedCert) - .getSerialNumber() - .toString()); + .getSerialNumber().toString()); } else { throw new CertificateException("Unable to retrieve datanode " + "certificate chain."); @@ -1255,6 +1250,14 @@ public String signAndStoreCertificate(PKCS10CertificationRequest request, } } + private void getAndStoreAllRootCAs(CertificateCodec certCodec) + throws IOException { + List rootCAPems = scmSecurityClient.getAllRootCaCertificates(); + for (String rootCAPem : rootCAPems) { + storeCertificate(rootCAPem, CAType.ROOT, certCodec, false); + } + } + public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } From f5bfba4bd69a51da3e4c8a04767b6bdde3a42afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 16:03:49 +0200 Subject: [PATCH 10/28] HDDS-8592 Add force parameter to CertificateRenewalService --- .../client/DefaultCertificateClient.java | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 74e715005615..117c357a0226 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1280,7 +1280,7 @@ public synchronized void startCertificateMonitor() { .setDaemon(true).build()); } this.executorService.scheduleAtFixedRate( - new CertificateRenewerService(this), + new CertificateRenewerService(this, false), timeBeforeGracePeriod, interval, TimeUnit.MILLISECONDS); getLogger().info("CertificateLifetimeMonitor for {} is started with " + "first delay {} ms and interval {} ms.", component, @@ -1292,9 +1292,12 @@ public synchronized void startCertificateMonitor() { */ public class CertificateRenewerService implements Runnable { private CertificateClient certClient; + private boolean forceRenewal; - public CertificateRenewerService(CertificateClient client) { + public CertificateRenewerService(CertificateClient client, + boolean forceRenewal) { this.certClient = client; + this.forceRenewal = forceRenewal; } @Override @@ -1309,40 +1312,41 @@ public void run() { synchronized (DefaultCertificateClient.class) { X509Certificate currentCert = getCertificate(); Duration timeLeft = timeBeforeExpiryGracePeriod(currentCert); - if (timeLeft.isZero()) { - String newCertId; - try { - getLogger().info("Current certificate has entered the expiry" + - " grace period {}. Starting renew key and certs.", - timeLeft, securityConfig.getRenewalGracePeriod()); - newCertId = renewAndStoreKeyAndCertificate(false); - } catch (CertificateException e) { - if (e.errorCode() == - CertificateException.ErrorCode.ROLLBACK_ERROR) { - if (shutdownCallback != null) { - getLogger().error("Failed to rollback key and cert after an " + - " unsuccessful renew try.", e); - shutdownCallback.run(); - } + if (!forceRenewal && !timeLeft.isZero()) { + return; + } + String newCertId; + try { + getLogger().info("Current certificate has entered the expiry" + + " grace period {}. Starting renew key and certs.", + timeLeft, securityConfig.getRenewalGracePeriod()); + newCertId = renewAndStoreKeyAndCertificate(forceRenewal); + } catch (CertificateException e) { + if (e.errorCode() == + CertificateException.ErrorCode.ROLLBACK_ERROR) { + if (shutdownCallback != null) { + getLogger().error("Failed to rollback key and cert after an " + + " unsuccessful renew try.", e); + shutdownCallback.run(); } - getLogger().error("Failed to renew and store key and cert." + - " Keep using existing certificates.", e); - return; - } - - // Persist new cert serial id in component VERSION file - if (certIdSaveCallback != null) { - certIdSaveCallback.accept(newCertId); } + getLogger().error("Failed to renew and store key and cert." + + " Keep using existing certificates.", e); + return; + } - // reset and reload all certs - reloadKeyAndCertificate(newCertId); - // cleanup backup directory - cleanBackupDir(); - // notify notification receivers - notificationReceivers.forEach(r -> r.notifyCertificateRenewed( - certClient, currentCert.getSerialNumber().toString(), newCertId)); + // Persist new cert serial id in component VERSION file + if (certIdSaveCallback != null) { + certIdSaveCallback.accept(newCertId); } + + // reset and reload all certs + reloadKeyAndCertificate(newCertId); + // cleanup backup directory + cleanBackupDir(); + // notify notification receivers + notificationReceivers.forEach(r -> r.notifyCertificateRenewed( + certClient, currentCert.getSerialNumber().toString(), newCertId)); } } } From 0b3754876c7eae269b7d515be5b68886373b7551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 16:07:30 +0200 Subject: [PATCH 11/28] HDDS-8592 Change visibility of the method that shouldn't be seen from outside, fix @Override --- .../x509/certificate/client/DefaultCertificateClient.java | 3 ++- .../x509/certificate/client/SCMCertificateClient.java | 3 +-- .../x509/certificate/client/TestDefaultCertificateClient.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 117c357a0226..80fd8d39a510 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1209,13 +1209,14 @@ private synchronized String updateCertSerialId(String newCertSerialId) { protected abstract SCMGetCertResponseProto getCertificateSignResponse( PKCS10CertificationRequest request) throws IOException; + @Override public String signAndStoreCertificate(PKCS10CertificationRequest request) throws CertificateException { return signAndStoreCertificate(request, securityConfig.getCertificateLocation(getComponentName())); } - public String signAndStoreCertificate(PKCS10CertificationRequest request, + private String signAndStoreCertificate(PKCS10CertificationRequest request, Path certificatePath) throws CertificateException { try { SCMGetCertResponseProto response = diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java index 9cbf51582c70..0c752f96f33b 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java @@ -158,8 +158,7 @@ protected SCMGetCertResponseProto getCertificateSignResponse( } @Override - public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certPath) { + public String signAndStoreCertificate(PKCS10CertificationRequest request) { throw new UnsupportedOperationException("signAndStoreCertificate of " + " SCMCertificateClient is not supported currently"); } diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java index b20b6c3180a4..395ae46ae67b 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java @@ -491,7 +491,7 @@ protected SCMGetCertResponseProto getCertificateSignResponse( @Override public String signAndStoreCertificate( - PKCS10CertificationRequest request, Path certificatePath) { + PKCS10CertificationRequest request) { return null; } }) { @@ -632,7 +632,7 @@ protected SCMGetCertResponseProto getCertificateSignResponse( @Override public String signAndStoreCertificate( - PKCS10CertificationRequest request, Path certificatePath) { + PKCS10CertificationRequest request) { return ""; } }; From cc9ffca79f0c789c830c90730b56110546f1fb54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 17:45:34 +0200 Subject: [PATCH 12/28] HDDS-8592 create function for providing task to root ca rotation processing. --- .../certificate/client/DefaultCertificateClient.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 80fd8d39a510..fa94764ac7bc 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -51,6 +51,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -1263,6 +1264,15 @@ public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } + public CompletableFuture getRootCARotationProcessor( + List rootCAs) { + if (rootCaCertificates.containsAll(rootCAs)) { + return CompletableFuture.completedFuture(null); + } + return CompletableFuture.runAsync(() -> new CertificateRenewerService( + this, true), executorService); + } + public synchronized void startCertificateMonitor() { Preconditions.checkNotNull(getCertificate(), "Component certificate should not be empty"); From 0bbacb35d28920b1541c57412354c0d60ab8dcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 17:55:03 +0200 Subject: [PATCH 13/28] HDDS-8592 remove unused import --- .../security/x509/certificate/client/SCMCertificateClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java index 0c752f96f33b..b561f984e5ba 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyPair; From cfa3ab689050fb776f87ebfe02d1e9f893050551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 18:08:56 +0200 Subject: [PATCH 14/28] HDDS-8591 refine test case for the poller not activating --- .../certificate/utils/TestRootCaRotationPoller.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java index 461b1c4cc42a..59c73f60b3a9 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -65,12 +65,18 @@ public void setup() { } @Test - public void testPollerDoesNotInvokeRootCaProcessor() throws IOException { + public void testPollerDoesNotInvokeRootCaProcessor() throws Exception { + X509Certificate knownCert = generateX509Cert( + LocalDateTime.now(), Duration.ofSeconds(50)); + HashSet knownCerts = new HashSet<>(); + knownCerts.add(knownCert); + List certsFromScm = new ArrayList<>(); + certsFromScm.add(CertificateCodec.getPEMEncodedString(knownCert)); RootCaRotationPoller poller = new RootCaRotationPoller(secConf, - new HashSet<>(), scmSecurityClient); + knownCerts, scmSecurityClient); Mockito.when(scmSecurityClient.getAllRootCaCertificates()) - .thenReturn(new ArrayList<>()); + .thenReturn(certsFromScm); AtomicBoolean atomicBoolean = new AtomicBoolean(); atomicBoolean.set(false); poller.addRootCARotationProcessor( From a85188574d1806b39fc7b5ebf7f71013b8fcc113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 18:09:36 +0200 Subject: [PATCH 15/28] HDDS-8591 remove unused import --- .../x509/certificate/utils/TestRootCaRotationPoller.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java index 59c73f60b3a9..e102310b3f00 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -32,7 +32,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.io.IOException; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Duration; From 164caafbfd5244dcf3ab8fec89119d84ad13c493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 18:23:29 +0200 Subject: [PATCH 16/28] HDDS-8592 Refine log message to not always imply datanode certificate --- .../x509/certificate/client/DefaultCertificateClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index fa94764ac7bc..3433b7951082 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1241,7 +1241,7 @@ private String signAndStoreCertificate(PKCS10CertificationRequest request, CertificateCodec.getX509Certificate(pemEncodedCert) .getSerialNumber().toString()); } else { - throw new CertificateException("Unable to retrieve datanode " + + throw new CertificateException("Unable to retrieve " + "certificate chain."); } } catch (IOException | java.security.cert.CertificateException e) { From 650f9b17e4f249087a5d4998861f1bfad2e05366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Thu, 29 Jun 2023 11:31:21 +0200 Subject: [PATCH 17/28] HDDS-8592 Fix bug with biginteger, remove findbugs issue --- .../client/DefaultCertificateClient.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 3433b7951082..1e391535b343 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -51,7 +52,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -222,15 +222,17 @@ private void updateCachedData( } private synchronized void updateCachedRootCAId(String s) { + BigInteger candidateNewId = new BigInteger(s); if (rootCaCertId == null - || Long.parseLong(s) > Long.parseLong(rootCaCertId)) { + || new BigInteger(rootCaCertId).compareTo(candidateNewId) < 0) { rootCaCertId = s; } } private synchronized void updateCachedSubCAId(String s) { + BigInteger candidateNewId = new BigInteger(s); if (caCertId == null - || Long.parseLong(s) > Long.parseLong(caCertId)) { + || new BigInteger(caCertId).compareTo(candidateNewId) < 0) { caCertId = s; } } @@ -1264,15 +1266,6 @@ public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } - public CompletableFuture getRootCARotationProcessor( - List rootCAs) { - if (rootCaCertificates.containsAll(rootCAs)) { - return CompletableFuture.completedFuture(null); - } - return CompletableFuture.runAsync(() -> new CertificateRenewerService( - this, true), executorService); - } - public synchronized void startCertificateMonitor() { Preconditions.checkNotNull(getCertificate(), "Component certificate should not be empty"); From 74af9fd1f0182211dc3774c073c1a871d3ed44fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 18:35:44 +0200 Subject: [PATCH 18/28] HDDS-8593 Start up root ca rotation poller --- .../client/DefaultCertificateClient.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 1e391535b343..0f2774814049 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -52,6 +52,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -126,6 +127,7 @@ public abstract class DefaultCertificateClient implements CertificateClient { private Runnable shutdownCallback; private SCMSecurityProtocolClientSideTranslatorPB scmSecurityClient; private final Set notificationReceivers; + private RootCaRotationPoller rootCaRotationPoller; protected DefaultCertificateClient( SecurityConfig securityConfig, @@ -175,6 +177,13 @@ private synchronized void loadAllCertificates() { getLogger().warn("Component certificate was not loaded."); } } + if (rootCaRotationPoller == null) { + rootCaRotationPoller = new RootCaRotationPoller(securityConfig, + rootCaCertificates, scmSecurityClient); + rootCaRotationPoller.addRootCARotationProcessor( + this::getRootCARotationProcessor); + rootCaRotationPoller.run(); + } } private synchronized void readCertificateFile(Path filePath) { @@ -969,6 +978,10 @@ public synchronized void close() throws IOException { executorService = null; } + if (rootCaRotationPoller != null) { + rootCaRotationPoller.close(); + } + if (serverKeyStoresFactory != null) { serverKeyStoresFactory.destroy(); } @@ -1266,6 +1279,15 @@ public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } + public CompletableFuture getRootCARotationProcessor( + List rootCAs) { + if (rootCaCertificates.containsAll(rootCAs)) { + return CompletableFuture.completedFuture(null); + } + return CompletableFuture.runAsync(() -> new CertificateRenewerService( + this, true), executorService); + } + public synchronized void startCertificateMonitor() { Preconditions.checkNotNull(getCertificate(), "Component certificate should not be empty"); From 6c5ae84c28c1a906a15a1247f2492afe9d78fdea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Thu, 29 Jun 2023 14:10:49 +0200 Subject: [PATCH 19/28] HDDS-8592 Fix failing unit test because of missing mock --- .../apache/hadoop/ozone/TestHddsSecureDatanodeInit.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/TestHddsSecureDatanodeInit.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/TestHddsSecureDatanodeInit.java index eea522438fab..fd8f5416bd2d 100644 --- a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/TestHddsSecureDatanodeInit.java +++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/TestHddsSecureDatanodeInit.java @@ -25,6 +25,8 @@ import java.security.cert.CertificateExpiredException; import java.time.Duration; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; import org.apache.hadoop.fs.FileUtil; @@ -315,6 +317,10 @@ public void testCertificateRotation() throws Exception { when(scmClient.getDataNodeCertificateChain(anyObject(), anyString())) .thenReturn(responseProto); + List rootCaList = new ArrayList<>(); + rootCaList.add(pemCert); + when(scmClient.getAllRootCaCertificates()).thenReturn(rootCaList); + // check that new cert ID should not equal to current cert ID String certId = newCertHolder.getSerialNumber().toString(); Assert.assertFalse(certId.equals( @@ -337,6 +343,7 @@ public void testCertificateRotation() throws Exception { // test the second time certificate rotation, generate a new cert newCertHolder = generateX509CertHolder(null, null, Duration.ofSeconds(CERT_LIFETIME)); + rootCaList.remove(pemCert); pemCert = CertificateCodec.getPEMEncodedString(newCertHolder); responseProto = SCMSecurityProtocolProtos.SCMGetCertResponseProto .newBuilder().setResponseCode(SCMSecurityProtocolProtos @@ -347,6 +354,8 @@ public void testCertificateRotation() throws Exception { .build(); when(scmClient.getDataNodeCertificateChain(anyObject(), anyString())) .thenReturn(responseProto); + rootCaList.add(pemCert); + when(scmClient.getAllRootCaCertificates()).thenReturn(rootCaList); String certId2 = newCertHolder.getSerialNumber().toString(); // check after renew, client will have the new cert ID From 2a1403b85cdab5ef6d0e0218901b5fc610ae990d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Thu, 29 Jun 2023 14:59:21 +0200 Subject: [PATCH 20/28] HDDS-8593 Fix findbugs issues --- .../x509/certificate/client/DefaultCertificateClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 0f2774814049..99832106164b 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1279,13 +1279,14 @@ public SCMSecurityProtocolClientSideTranslatorPB getScmSecureClient() { return scmSecurityClient; } - public CompletableFuture getRootCARotationProcessor( + public synchronized CompletableFuture getRootCARotationProcessor( List rootCAs) { if (rootCaCertificates.containsAll(rootCAs)) { return CompletableFuture.completedFuture(null); } - return CompletableFuture.runAsync(() -> new CertificateRenewerService( - this, true), executorService); + CertificateRenewerService renewerService = new CertificateRenewerService( + this, true); + return CompletableFuture.runAsync(renewerService, executorService); } public synchronized void startCertificateMonitor() { From c7adcb06957143a043db294531ec487ca00a423c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Thu, 29 Jun 2023 20:23:35 +0200 Subject: [PATCH 21/28] HDDS-8593 Remove poller from SCMCertificateClient --- .../x509/certificate/client/DefaultCertificateClient.java | 4 ++++ .../x509/certificate/client/SCMCertificateClient.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index 99832106164b..91bee2f39495 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -177,6 +177,10 @@ private synchronized void loadAllCertificates() { getLogger().warn("Component certificate was not loaded."); } } + startRootCaRotationPoller(); + } + + protected void startRootCaRotationPoller() { if (rootCaRotationPoller == null) { rootCaRotationPoller = new RootCaRotationPoller(securityConfig, rootCaCertificates, scmSecurityClient); diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java index b561f984e5ba..26676a82b005 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java @@ -161,4 +161,9 @@ public String signAndStoreCertificate(PKCS10CertificationRequest request) { throw new UnsupportedOperationException("signAndStoreCertificate of " + " SCMCertificateClient is not supported currently"); } + + @Override + protected void startRootCaRotationPoller() { + //SCM root CA rotation is handled separately from polling + } } From d71938092f4f8469ff71a3d3cbe5894ac106bd3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Fri, 30 Jun 2023 16:20:27 +0200 Subject: [PATCH 22/28] HDDS-8591 Demonize thread for RootCaRotationPoller --- .../x509/certificate/client/RootCaRotationPoller.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index af06291aef5e..a49cfb0da7d2 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hdds.security.x509.certificate.client; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; @@ -61,7 +62,10 @@ public RootCaRotationPoller(SecurityConfig securityConfig, SCMSecurityProtocolClientSideTranslatorPB scmSecureClient) { this.scmSecureClient = scmSecureClient; this.knownRootCerts = initiallyKnownRootCaCerts; - poller = Executors.newSingleThreadScheduledExecutor(); + poller = Executors.newScheduledThreadPool(1, + new ThreadFactoryBuilder().setNameFormat( + "RootCaRotationPoller") + .setDaemon(true).build()); pollingRate = securityConfig.getRootCaClientPollingFrequency(); rootCARotationProcessors = new ArrayList<>(); } From ce518c3fff90e22f6c015e480d68473c9cf2b34d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 5 Jul 2023 15:18:37 +0200 Subject: [PATCH 23/28] HDDS-8591 Create root ca rotation poller --- .../apache/hadoop/hdds/HddsConfigKeys.java | 4 + .../hadoop/hdds/security/SecurityConfig.java | 14 ++ .../src/main/resources/ozone-default.xml | 11 ++ .../client/RootCaRotationPoller.java | 144 ++++++++++++++++++ .../utils/TestRootCaRotationPoller.java | 130 ++++++++++++++++ 5 files changed, 303 insertions(+) create mode 100644 hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java create mode 100644 hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java index ac6c08867be3..b58ab6996fdf 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java @@ -222,6 +222,10 @@ public final class HddsConfigKeys { "hdds.x509.ca.rotation.ack.timeout"; public static final String HDDS_X509_CA_ROTATION_ACK_TIMEOUT_DEFAULT = "PT15M"; + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY = + "hdds.x509.rootca.client.polling.frequency"; + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT + = "PT2h"; public static final String HDDS_CONTAINER_REPLICATION_COMPRESSION = "hdds.container.replication.compression"; diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index f3e747de63b7..b297f607e4c5 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -52,6 +52,8 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_CA_ROTATION_TIME_OF_DAY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PUBLIC_KEY_FILE; @@ -131,6 +133,7 @@ public class SecurityConfig { Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); private final Duration caAckTimeout; private final SslProvider grpcSSLProvider; + private final Duration rootCaClientPollingFrequency; /** * Constructs a SecurityConfig. @@ -228,6 +231,13 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); + String rootCaClientPollingFrequencyString = configuration.get( + HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, + HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT); + + this.rootCaClientPollingFrequency = + Duration.parse(rootCaClientPollingFrequencyString); + this.externalRootCaCert = configuration.get( HDDS_X509_ROOTCA_CERTIFICATE_FILE, HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT); @@ -552,6 +562,10 @@ public Duration getCaAckTimeout() { return caAckTimeout; } + public Duration getRootCaClientPollingFrequency() { + return rootCaClientPollingFrequency; + } + /** * Return true if using test certificates with authority as localhost. This * should be used only for unit test where certificates are generated by diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 484e5bfd3cd0..6f7b3c55fbce 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2268,6 +2268,17 @@ is failed. Default is 15 minutes. + + hdds.x509.rootca.client.polling.frequency + PT2h + Frequency to use for polling in certificate clients for a new + root ca certificate. Every time the specified time duration elapses, + the clients send a request to the SCMs to see if a new root ca + certificate was generated. Once there is a change, the system + automatically adds the new root ca to the clients' + trust stores and requests a new certificate to be signed. + + ozone.scm.security.handler.count.key 2 diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java new file mode 100644 index 000000000000..a49cfb0da7d2 --- /dev/null +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + *

+ * http://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.apache.hadoop.hdds.security.x509.certificate.client; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; +import org.apache.hadoop.hdds.security.SecurityConfig; +import org.apache.hadoop.ozone.OzoneSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.math.BigInteger; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Poller mechanism for Root Ca Rotation for clients. + */ +public class RootCaRotationPoller implements Runnable, Closeable { + + private static final Logger LOG = + LoggerFactory.getLogger(RootCaRotationPoller.class); + private final List, CompletableFuture>> + rootCARotationProcessors; + private final ScheduledExecutorService poller; + private final Duration pollingRate; + private Set knownRootCerts; + private final SCMSecurityProtocolClientSideTranslatorPB scmSecureClient; + + public RootCaRotationPoller(SecurityConfig securityConfig, + Set initiallyKnownRootCaCerts, + SCMSecurityProtocolClientSideTranslatorPB scmSecureClient) { + this.scmSecureClient = scmSecureClient; + this.knownRootCerts = initiallyKnownRootCaCerts; + poller = Executors.newScheduledThreadPool(1, + new ThreadFactoryBuilder().setNameFormat( + "RootCaRotationPoller") + .setDaemon(true).build()); + pollingRate = securityConfig.getRootCaClientPollingFrequency(); + rootCARotationProcessors = new ArrayList<>(); + } + + private void pollRootCas() { + try { + List pemEncodedRootCaList = + scmSecureClient.getAllRootCaCertificates(); + List rootCAsFromSCM = + OzoneSecurityUtil.convertToX509(pemEncodedRootCaList); + List scmCertsWithoutKnownCerts + = new ArrayList<>(rootCAsFromSCM); + scmCertsWithoutKnownCerts.removeAll(knownRootCerts); + if (scmCertsWithoutKnownCerts.isEmpty()) { + return; + } + LOG.info("Some root CAs are not known to the client out of the root " + + "CAs known to the SCMs. Root CA Cert ids known to the client: " + + getPrintableCertIds(knownRootCerts) + ". Root CA Cert ids from " + + "SCM not known by the client: " + + getPrintableCertIds(scmCertsWithoutKnownCerts)); + + CompletableFuture allRootCAProcessorFutures = + CompletableFuture.allOf(rootCARotationProcessors.stream() + .map(c -> c.apply(rootCAsFromSCM)) + .toArray(CompletableFuture[]::new)); + + allRootCAProcessorFutures.whenComplete((unused, throwable) -> { + if (throwable == null) { + knownRootCerts = new HashSet<>(rootCAsFromSCM); + } + }); + } catch (IOException e) { + LOG.error("Error while trying to rotate root ca certificate", e); + } + } + + public void addRootCARotationProcessor( + Function, CompletableFuture> processor) { + rootCARotationProcessors.add(processor); + } + + @Override + public void run() { + poller.scheduleAtFixedRate(this::pollRootCas, 0, + pollingRate.getSeconds(), TimeUnit.SECONDS); + } + + @Override + public void close() { + executorServiceShutdownGraceful(poller); + } + + private void executorServiceShutdownGraceful(ExecutorService executor) { + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + LOG.error("Unable to shutdown root ca certificate rotation poller."); + } + } catch (InterruptedException e) { + LOG.error("Error attempting to shutdown.", e); + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + private String getPrintableCertIds(Collection certs) { + return StringUtils.join(certs.stream() + .map(X509Certificate::getSerialNumber) + .map(BigInteger::toString) + .collect(Collectors.toList()), ", "); + } +} diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java new file mode 100644 index 000000000000..e102310b3f00 --- /dev/null +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.hadoop.hdds.security.x509.certificate.utils; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; +import org.apache.hadoop.hdds.security.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.client.RootCaRotationPoller; +import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.apache.ozone.test.GenericTestUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; + +/** + * Test for Root Ca Rotation polling mechanism on client side. + */ +public class TestRootCaRotationPoller { + + private SecurityConfig secConf; + + @Mock + private SCMSecurityProtocolClientSideTranslatorPB scmSecurityClient; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + OzoneConfiguration conf = new OzoneConfiguration(); + conf.set(HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, "PT1s"); + secConf = new SecurityConfig(conf); + } + + @Test + public void testPollerDoesNotInvokeRootCaProcessor() throws Exception { + X509Certificate knownCert = generateX509Cert( + LocalDateTime.now(), Duration.ofSeconds(50)); + HashSet knownCerts = new HashSet<>(); + knownCerts.add(knownCert); + List certsFromScm = new ArrayList<>(); + certsFromScm.add(CertificateCodec.getPEMEncodedString(knownCert)); + RootCaRotationPoller poller = new RootCaRotationPoller(secConf, + knownCerts, scmSecurityClient); + + Mockito.when(scmSecurityClient.getAllRootCaCertificates()) + .thenReturn(certsFromScm); + AtomicBoolean atomicBoolean = new AtomicBoolean(); + atomicBoolean.set(false); + poller.addRootCARotationProcessor( + certificates -> CompletableFuture.supplyAsync(() -> { + atomicBoolean.set(true); + Assertions.assertEquals(certificates.size(), 2); + return null; + })); + poller.run(); + Assertions.assertThrows(TimeoutException.class, () -> + GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000)); + } + + @Test + public void testPollerInvokesRootCaProcessors() throws Exception { + X509Certificate knownCert = generateX509Cert( + LocalDateTime.now(), Duration.ofSeconds(50)); + X509Certificate newRootCa = generateX509Cert( + LocalDateTime.now(), Duration.ofSeconds(50)); + HashSet knownCerts = new HashSet<>(); + knownCerts.add(knownCert); + List certsFromScm = new ArrayList<>(); + certsFromScm.add(CertificateCodec.getPEMEncodedString(knownCert)); + certsFromScm.add(CertificateCodec.getPEMEncodedString(newRootCa)); + RootCaRotationPoller poller = new RootCaRotationPoller(secConf, + knownCerts, scmSecurityClient); + poller.run(); + Mockito.when(scmSecurityClient.getAllRootCaCertificates()) + .thenReturn(certsFromScm); + AtomicBoolean atomicBoolean = new AtomicBoolean(); + atomicBoolean.set(false); + poller.addRootCARotationProcessor( + certificates -> CompletableFuture.supplyAsync(() -> { + atomicBoolean.set(true); + Assertions.assertEquals(certificates.size(), 2); + return null; + })); + GenericTestUtils.waitFor(atomicBoolean::get, 50, 5000); + } + + private X509Certificate generateX509Cert( + LocalDateTime startDate, Duration certLifetime) throws Exception { + KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); + LocalDateTime start = startDate == null ? LocalDateTime.now() : startDate; + LocalDateTime end = start.plus(certLifetime); + return new JcaX509CertificateConverter().getCertificate( + SelfSignedCertificate.newBuilder().setBeginDate(start) + .setEndDate(end).setClusterID("cluster").setKey(keyPair) + .setSubject("localhost").setConfiguration(secConf).setScmID("test") + .build()); + } +} From a89ce735415b545b259efbc7cf05dbfb01eb4b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 5 Jul 2023 15:27:04 +0200 Subject: [PATCH 24/28] HDDS-8591 Replace the phrase interval instead of frequency --- .../java/org/apache/hadoop/hdds/HddsConfigKeys.java | 6 +++--- .../apache/hadoop/hdds/security/SecurityConfig.java | 10 +++++----- .../common/src/main/resources/ozone-default.xml | 4 ++-- .../x509/certificate/client/RootCaRotationPoller.java | 6 +++--- .../certificate/utils/TestRootCaRotationPoller.java | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java index b58ab6996fdf..ba3df6447a5d 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java @@ -222,9 +222,9 @@ public final class HddsConfigKeys { "hdds.x509.ca.rotation.ack.timeout"; public static final String HDDS_X509_CA_ROTATION_ACK_TIMEOUT_DEFAULT = "PT15M"; - public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY = - "hdds.x509.rootca.client.polling.frequency"; - public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL = + "hdds.x509.rootca.client.polling.interval"; + public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT = "PT2h"; public static final String HDDS_CONTAINER_REPLICATION_COMPRESSION = diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index b297f607e4c5..fcea09580423 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -52,8 +52,8 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_CA_ROTATION_TIME_OF_DAY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PUBLIC_KEY_FILE; @@ -232,8 +232,8 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); String rootCaClientPollingFrequencyString = configuration.get( - HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, - HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY_DEFAULT); + HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL, + HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT); this.rootCaClientPollingFrequency = Duration.parse(rootCaClientPollingFrequencyString); @@ -562,7 +562,7 @@ public Duration getCaAckTimeout() { return caAckTimeout; } - public Duration getRootCaClientPollingFrequency() { + public Duration getRootCaClientPollingInterval() { return rootCaClientPollingFrequency; } diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 6f7b3c55fbce..c610622156c8 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2269,9 +2269,9 @@ - hdds.x509.rootca.client.polling.frequency + hdds.x509.rootca.client.polling.interval PT2h - Frequency to use for polling in certificate clients for a new + Interval to use for polling in certificate clients for a new root ca certificate. Every time the specified time duration elapses, the clients send a request to the SCMs to see if a new root ca certificate was generated. Once there is a change, the system diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index a49cfb0da7d2..690d6085a4a4 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -53,7 +53,7 @@ public class RootCaRotationPoller implements Runnable, Closeable { private final List, CompletableFuture>> rootCARotationProcessors; private final ScheduledExecutorService poller; - private final Duration pollingRate; + private final Duration pollingInterval; private Set knownRootCerts; private final SCMSecurityProtocolClientSideTranslatorPB scmSecureClient; @@ -66,7 +66,7 @@ public RootCaRotationPoller(SecurityConfig securityConfig, new ThreadFactoryBuilder().setNameFormat( "RootCaRotationPoller") .setDaemon(true).build()); - pollingRate = securityConfig.getRootCaClientPollingFrequency(); + pollingInterval = securityConfig.getRootCaClientPollingInterval(); rootCARotationProcessors = new ArrayList<>(); } @@ -111,7 +111,7 @@ public void addRootCARotationProcessor( @Override public void run() { poller.scheduleAtFixedRate(this::pollRootCas, 0, - pollingRate.getSeconds(), TimeUnit.SECONDS); + pollingInterval.getSeconds(), TimeUnit.SECONDS); } @Override diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java index e102310b3f00..41f97d7bf1f7 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -43,7 +43,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL; /** * Test for Root Ca Rotation polling mechanism on client side. @@ -59,7 +59,7 @@ public class TestRootCaRotationPoller { public void setup() { MockitoAnnotations.openMocks(this); OzoneConfiguration conf = new OzoneConfiguration(); - conf.set(HDDS_X509_ROOTCA_CLIENT_POLLING_FREQUENCY, "PT1s"); + conf.set(HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL, "PT1s"); secConf = new SecurityConfig(conf); } From a9296fc49b76b514609191bfc42b1efb66e5fdaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 5 Jul 2023 15:31:08 +0200 Subject: [PATCH 25/28] HDDS-8591 Replace the phrase interval instead of frequency --- .../apache/hadoop/hdds/security/SecurityConfig.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index fcea09580423..d38a9a3016c6 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -133,7 +133,7 @@ public class SecurityConfig { Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); private final Duration caAckTimeout; private final SslProvider grpcSSLProvider; - private final Duration rootCaClientPollingFrequency; + private final Duration rootCaClientPollingInterval; /** * Constructs a SecurityConfig. @@ -231,12 +231,12 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); - String rootCaClientPollingFrequencyString = configuration.get( + String rootCaClientPollingIntervalString = configuration.get( HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL, HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT); - this.rootCaClientPollingFrequency = - Duration.parse(rootCaClientPollingFrequencyString); + this.rootCaClientPollingInterval = + Duration.parse(rootCaClientPollingIntervalString); this.externalRootCaCert = configuration.get( HDDS_X509_ROOTCA_CERTIFICATE_FILE, @@ -563,7 +563,7 @@ public Duration getCaAckTimeout() { } public Duration getRootCaClientPollingInterval() { - return rootCaClientPollingFrequency; + return rootCaClientPollingInterval; } /** From 546757a534f9df64868b03e2c0accea003a2a3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 5 Jul 2023 16:46:42 +0200 Subject: [PATCH 26/28] HDDS-8591 Address renew comments --- .../org/apache/hadoop/hdds/HddsConfigKeys.java | 8 ++++---- .../hadoop/hdds/security/SecurityConfig.java | 8 ++++---- .../common/src/main/resources/ozone-default.xml | 2 +- .../certificate/client/RootCaRotationPoller.java | 15 +++++++-------- .../utils/TestRootCaRotationPoller.java | 4 ++-- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java index ba3df6447a5d..e1edd945532d 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java @@ -222,10 +222,10 @@ public final class HddsConfigKeys { "hdds.x509.ca.rotation.ack.timeout"; public static final String HDDS_X509_CA_ROTATION_ACK_TIMEOUT_DEFAULT = "PT15M"; - public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL = - "hdds.x509.rootca.client.polling.interval"; - public static final String HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT - = "PT2h"; + public static final String HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL = + "hdds.x509.rootca.certificate.polling.interval"; + public static final String + HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL_DEFAULT = "PT2h"; public static final String HDDS_CONTAINER_REPLICATION_COMPRESSION = "hdds.container.replication.compression"; diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index d38a9a3016c6..78f7491c4f33 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -52,8 +52,8 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_CA_ROTATION_TIME_OF_DAY_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_FILE_DEFAULT; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PRIVATE_KEY_FILE_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_PUBLIC_KEY_FILE; @@ -232,8 +232,8 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); String rootCaClientPollingIntervalString = configuration.get( - HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL, - HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL_DEFAULT); + HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL, + HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL_DEFAULT); this.rootCaClientPollingInterval = Duration.parse(rootCaClientPollingIntervalString); diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index c610622156c8..ce65be861dee 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2269,7 +2269,7 @@ - hdds.x509.rootca.client.polling.interval + hdds.x509.rootca.certificate.polling.interval PT2h Interval to use for polling in certificate clients for a new root ca certificate. Every time the specified time duration elapses, diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index 690d6085a4a4..1756dcc496bf 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -18,7 +18,6 @@ package org.apache.hadoop.hdds.security.x509.certificate.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.SecurityConfig; import org.apache.hadoop.ozone.OzoneSecurityUtil; @@ -64,7 +63,7 @@ public RootCaRotationPoller(SecurityConfig securityConfig, this.knownRootCerts = initiallyKnownRootCaCerts; poller = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat( - "RootCaRotationPoller") + this.getClass().getSimpleName()) .setDaemon(true).build()); pollingInterval = securityConfig.getRootCaClientPollingInterval(); rootCARotationProcessors = new ArrayList<>(); @@ -99,7 +98,7 @@ private void pollRootCas() { } }); } catch (IOException e) { - LOG.error("Error while trying to rotate root ca certificate", e); + LOG.error("Error while trying to poll root ca certificate", e); } } @@ -126,19 +125,19 @@ private void executorServiceShutdownGraceful(ExecutorService executor) { executor.shutdownNow(); } if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - LOG.error("Unable to shutdown root ca certificate rotation poller."); + LOG.warn("{} couldn't be shut down gracefully", + getClass().getSimpleName()); } } catch (InterruptedException e) { - LOG.error("Error attempting to shutdown.", e); - executor.shutdownNow(); + LOG.warn("{} couldn't be stopped gracefully", getClass().getSimpleName()); Thread.currentThread().interrupt(); } } private String getPrintableCertIds(Collection certs) { - return StringUtils.join(certs.stream() + return certs.stream() .map(X509Certificate::getSerialNumber) .map(BigInteger::toString) - .collect(Collectors.toList()), ", "); + .collect(Collectors.joining(", ")); } } diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java index 41f97d7bf1f7..7f2ed0a8135c 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCaRotationPoller.java @@ -43,7 +43,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL; /** * Test for Root Ca Rotation polling mechanism on client side. @@ -59,7 +59,7 @@ public class TestRootCaRotationPoller { public void setup() { MockitoAnnotations.openMocks(this); OzoneConfiguration conf = new OzoneConfiguration(); - conf.set(HDDS_X509_ROOTCA_CLIENT_POLLING_INTERVAL, "PT1s"); + conf.set(HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL, "PT1s"); secConf = new SecurityConfig(conf); } From 859c2f9431c68b3777f290128576b3460887cc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 5 Jul 2023 16:55:23 +0200 Subject: [PATCH 27/28] HDDS-8591 Fix missing client -> certificate rename --- .../apache/hadoop/hdds/security/SecurityConfig.java | 12 ++++++------ .../certificate/client/RootCaRotationPoller.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java index 78f7491c4f33..d2bd588d095d 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/SecurityConfig.java @@ -133,7 +133,7 @@ public class SecurityConfig { Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); private final Duration caAckTimeout; private final SslProvider grpcSSLProvider; - private final Duration rootCaClientPollingInterval; + private final Duration rootCaCertificatePollingInterval; /** * Constructs a SecurityConfig. @@ -231,12 +231,12 @@ public SecurityConfig(ConfigurationSource configuration) { validateCertificateValidityConfig(); - String rootCaClientPollingIntervalString = configuration.get( + String rootCaCertificatePollingIntervalString = configuration.get( HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL, HDDS_X509_ROOTCA_CERTIFICATE_POLLING_INTERVAL_DEFAULT); - this.rootCaClientPollingInterval = - Duration.parse(rootCaClientPollingIntervalString); + this.rootCaCertificatePollingInterval = + Duration.parse(rootCaCertificatePollingIntervalString); this.externalRootCaCert = configuration.get( HDDS_X509_ROOTCA_CERTIFICATE_FILE, @@ -562,8 +562,8 @@ public Duration getCaAckTimeout() { return caAckTimeout; } - public Duration getRootCaClientPollingInterval() { - return rootCaClientPollingInterval; + public Duration getRootCaCertificatePollingInterval() { + return rootCaCertificatePollingInterval; } /** diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java index 1756dcc496bf..47cc368bbefe 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/RootCaRotationPoller.java @@ -65,7 +65,7 @@ public RootCaRotationPoller(SecurityConfig securityConfig, new ThreadFactoryBuilder().setNameFormat( this.getClass().getSimpleName()) .setDaemon(true).build()); - pollingInterval = securityConfig.getRootCaClientPollingInterval(); + pollingInterval = securityConfig.getRootCaCertificatePollingInterval(); rootCARotationProcessors = new ArrayList<>(); } From 070389fe7d74bd9e94c0f85909095ab49259a313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20G=C3=A1l?= Date: Wed, 28 Jun 2023 08:42:11 +0200 Subject: [PATCH 28/28] HDDS-8592 Rename CertificateLifeTimeMonitor --- .../certificate/client/DefaultCertificateClient.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index abd2beec506c..bde318d93b2d 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -1268,7 +1268,8 @@ public synchronized void startCertificateMonitor() { getComponentName() + "-CertificateLifetimeMonitor") .setDaemon(true).build()); } - this.executorService.scheduleAtFixedRate(new CertificateLifetimeMonitor(), + this.executorService.scheduleAtFixedRate( + new CertificateRenewerService(this), timeBeforeGracePeriod, interval, TimeUnit.MILLISECONDS); getLogger().info("CertificateLifetimeMonitor for {} is started with " + "first delay {} ms and interval {} ms.", component, @@ -1278,9 +1279,11 @@ public synchronized void startCertificateMonitor() { /** * Task to monitor certificate lifetime and renew the certificate if needed. */ - public class CertificateLifetimeMonitor implements Runnable { + public class CertificateRenewerService implements Runnable { + private CertificateClient certClient; - public CertificateLifetimeMonitor() { + public CertificateRenewerService(CertificateClient client) { + this.certClient = client; } @Override