This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 66e277386e HDDS-7572. Use keyManager and trustManager provided by
keyStoreFactory in datanode grpc services (#4032)
66e277386e is described below
commit 66e277386e5fadf5ee62184654a5b0f57aa2948b
Author: Sammi Chen <[email protected]>
AuthorDate: Mon Dec 12 10:15:27 2022 +0800
HDDS-7572. Use keyManager and trustManager provided by keyStoreFactory in
datanode grpc services (#4032)
---
.../apache/hadoop/hdds/scm/XceiverClientGrpc.java | 2 +-
.../ozone/container/ContainerTestHelper.java | 18 +-
.../apache/hadoop/ozone/HddsDatanodeService.java | 8 +
.../common/transport/server/XceiverServerGrpc.java | 4 +-
.../replication/GrpcReplicationClient.java | 16 +-
.../container/replication/ReplicationServer.java | 26 +--
.../replication/SimpleContainerDownloader.java | 7 +-
.../x509/certificate/client/CertificateClient.java | 13 +-
.../client/DefaultCertificateClient.java | 35 ++++
.../hdds/security/x509/keys/SecurityUtil.java | 35 ++++
.../hdds/security/x509/CertificateClientTest.java | 17 ++
.../ozone/client/CertificateClientTestImpl.java | 136 ++++++++++++---
.../ozoneimpl/TestOzoneContainerWithTLS.java | 185 +++++++++++++++++++--
13 files changed, 427 insertions(+), 75 deletions(-)
diff --git
a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java
b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java
index cab7bee780..1fee58c3b4 100644
---
a/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java
+++
b/hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java
@@ -198,7 +198,7 @@ public class XceiverClientGrpc extends XceiverClientSpi {
NettyChannelBuilder.forAddress(dn.getIpAddress(), port).usePlaintext()
.maxInboundMessageSize(OzoneConsts.OZONE_SCM_CHUNK_MAX_SIZE)
.intercept(new GrpcClientInterceptor());
- if (secConfig.isGrpcTlsEnabled()) {
+ if (secConfig.isSecurityEnabled() && secConfig.isGrpcTlsEnabled()) {
SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
if (caCerts != null) {
sslContextBuilder.trustManager(caCerts);
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java
index 09fff2371e..327e03d70d 100644
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/ozone/container/ContainerTestHelper.java
@@ -470,19 +470,29 @@ public final class ContainerTestHelper {
* Returns a close container request.
* @param pipeline - pipeline
* @param containerID - ID of the container.
+ * @param token - container token
* @return ContainerCommandRequestProto.
*/
public static ContainerCommandRequestProto getCloseContainer(
- Pipeline pipeline, long containerID) throws IOException {
- return ContainerCommandRequestProto.newBuilder()
+ Pipeline pipeline, long containerID, Token<?> token) throws IOException {
+ Builder builder = ContainerCommandRequestProto.newBuilder()
.setCmdType(ContainerProtos.Type.CloseContainer)
.setContainerID(containerID)
.setCloseContainer(
ContainerProtos.CloseContainerRequestProto.getDefaultInstance())
- .setDatanodeUuid(pipeline.getFirstNode().getUuidString())
- .build();
+ .setDatanodeUuid(pipeline.getFirstNode().getUuidString());
+
+ if (token != null) {
+ builder.setEncodedToken(token.encodeToUrlString());
+ }
+
+ return builder.build();
}
+ public static ContainerCommandRequestProto getCloseContainer(
+ Pipeline pipeline, long containerID) throws IOException {
+ return getCloseContainer(pipeline, containerID, null);
+ }
/**
* Returns a simple request without traceId.
* @param pipeline - pipeline
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
index 45b453f4a7..2de1bde537 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/HddsDatanodeService.java
@@ -604,6 +604,14 @@ public class HddsDatanodeService extends GenericCli
implements ServicePlugin {
}
}
}
+
+ if (dnCertClient != null) {
+ try {
+ dnCertClient.close();
+ } catch (IOException e) {
+ LOG.warn("Certificate client could not be closed", e);
+ }
+ }
}
@VisibleForTesting
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/XceiverServerGrpc.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/XceiverServerGrpc.java
index c1fc950792..528f4b8bd7 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/XceiverServerGrpc.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/XceiverServerGrpc.java
@@ -140,10 +140,10 @@ public final class XceiverServerGrpc implements
XceiverServerSpi {
new GrpcXceiverService(dispatcher), new GrpcServerInterceptor()));
SecurityConfig secConf = new SecurityConfig(conf);
- if (secConf.isGrpcTlsEnabled()) {
+ if (secConf.isSecurityEnabled() && secConf.isGrpcTlsEnabled()) {
try {
SslContextBuilder sslClientContextBuilder =
SslContextBuilder.forServer(
- caClient.getPrivateKey(), caClient.getCertificate());
+ caClient.getServerKeyStoresFactory().getKeyManagers()[0]);
SslContextBuilder sslContextBuilder = GrpcSslContexts.configure(
sslClientContextBuilder, secConf.getGrpcSslProvider());
nettyServerBuilder.sslContext(sslContextBuilder.build());
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/GrpcReplicationClient.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/GrpcReplicationClient.java
index 023b251a52..e1348614e7 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/GrpcReplicationClient.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/GrpcReplicationClient.java
@@ -31,9 +31,9 @@ import
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.CopyContai
import
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.CopyContainerResponseProto;
import
org.apache.hadoop.hdds.protocol.datanode.proto.IntraDatanodeProtocolServiceGrpc;
import
org.apache.hadoop.hdds.protocol.datanode.proto.IntraDatanodeProtocolServiceGrpc.IntraDatanodeProtocolServiceStub;
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
-import org.apache.hadoop.hdds.utils.HAUtils;
import org.apache.hadoop.ozone.OzoneConsts;
import com.google.common.base.Preconditions;
@@ -60,10 +60,9 @@ public class GrpcReplicationClient implements AutoCloseable {
private final Path workingDirectory;
- public GrpcReplicationClient(
- String host, int port, Path workingDir,
- SecurityConfig secConfig, CertificateClient certClient
- ) throws IOException {
+ public GrpcReplicationClient(String host, int port, Path workingDir,
+ SecurityConfig secConfig, CertificateClient certClient)
+ throws IOException {
NettyChannelBuilder channelBuilder =
NettyChannelBuilder.forAddress(host, port)
.usePlaintext()
@@ -74,12 +73,11 @@ public class GrpcReplicationClient implements AutoCloseable
{
SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
if (certClient != null) {
+ KeyStoresFactory factory = certClient.getClientKeyStoresFactory();
sslContextBuilder
- .trustManager(HAUtils.buildCAX509List(certClient,
- secConfig.getConfiguration()))
+ .trustManager(factory.getTrustManagers()[0])
.clientAuth(ClientAuth.REQUIRE)
- .keyManager(certClient.getPrivateKey(),
- certClient.getCertificate());
+ .keyManager(factory.getKeyManagers()[0]);
}
if (secConfig.useTestCert()) {
channelBuilder.overrideAuthority("localhost");
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/ReplicationServer.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/ReplicationServer.java
index bf8d6f1025..fcad690d4f 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/ReplicationServer.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/ReplicationServer.java
@@ -27,7 +27,6 @@ import org.apache.hadoop.hdds.conf.PostConstruct;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import org.apache.hadoop.hdds.tracing.GrpcServerInterceptor;
-import org.apache.hadoop.hdds.utils.HAUtils;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.container.ozoneimpl.ContainerController;
@@ -61,12 +60,9 @@ public class ReplicationServer {
private int port;
- public ReplicationServer(
- ContainerController controller,
- ReplicationConfig replicationConfig,
- SecurityConfig secConf,
- CertificateClient caClient
- ) {
+ public ReplicationServer(ContainerController controller,
+ ReplicationConfig replicationConfig, SecurityConfig secConf,
+ CertificateClient caClient) {
this.secConf = secConf;
this.caClient = caClient;
this.controller = controller;
@@ -81,17 +77,17 @@ public class ReplicationServer {
new OnDemandContainerReplicationSource(controller)
), new GrpcServerInterceptor()));
- if (secConf.isSecurityEnabled()) {
+ if (secConf.isSecurityEnabled() && secConf.isGrpcTlsEnabled()) {
try {
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(
- caClient.getPrivateKey(), caClient.getCertificate());
+ caClient.getServerKeyStoresFactory().getKeyManagers()[0]);
sslContextBuilder = GrpcSslContexts.configure(
sslContextBuilder, secConf.getGrpcSslProvider());
sslContextBuilder.clientAuth(ClientAuth.REQUIRE);
- sslContextBuilder.trustManager(HAUtils.buildCAX509List(caClient,
- secConf.getConfiguration()));
+ sslContextBuilder.trustManager(
+ caClient.getServerKeyStoresFactory().getTrustManagers()[0]);
nettyServerBuilder.sslContext(sslContextBuilder.build());
} catch (IOException ex) {
@@ -106,14 +102,8 @@ public class ReplicationServer {
public void start() throws IOException {
server.start();
-
- if (port == 0) {
- LOG.info("{} is started using port {}", getClass().getSimpleName(),
- server.getPort());
- }
-
port = server.getPort();
-
+ LOG.info("{} is started using port {}", getClass().getSimpleName(), port);
}
public void stop() {
diff --git
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/SimpleContainerDownloader.java
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/SimpleContainerDownloader.java
index 8e661635fe..18fe058728 100644
---
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/SimpleContainerDownloader.java
+++
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/replication/SimpleContainerDownloader.java
@@ -47,16 +47,15 @@ import org.slf4j.LoggerFactory;
*/
public class SimpleContainerDownloader implements ContainerDownloader {
- private static final Logger LOG =
+ public static final Logger LOG =
LoggerFactory.getLogger(SimpleContainerDownloader.class);
private final Path workingDirectory;
private final SecurityConfig securityConfig;
private final CertificateClient certClient;
- public SimpleContainerDownloader(
- ConfigurationSource conf, CertificateClient certClient) {
-
+ public SimpleContainerDownloader(ConfigurationSource conf,
+ CertificateClient certClient) {
String workDirString =
conf.get(OzoneConfigKeys.OZONE_CONTAINER_COPY_WORKDIR);
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
index e2863b9426..ce7cb04855 100644
---
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
@@ -20,10 +20,12 @@
package org.apache.hadoop.hdds.security.x509.certificate.client;
import org.apache.hadoop.hdds.security.OzoneSecurityException;
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
import
org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
@@ -39,7 +41,7 @@ import static
org.apache.hadoop.hdds.security.OzoneSecurityException.ResultCodes
* Certificate client provides and interface to certificate operations that
* needs to be performed by all clients in the Ozone eco-system.
*/
-public interface CertificateClient {
+public interface CertificateClient extends Closeable {
/**
* Returns the private key of the specified component if it exists on the
@@ -323,4 +325,13 @@ public interface CertificateClient {
*/
boolean processCrl(CRLInfo crl);
+ /**
+ * Return the store factory for key manager and trust manager for server.
+ */
+ KeyStoresFactory getServerKeyStoresFactory() throws CertificateException;
+
+ /**
+ * Return the store factory for key manager and trust manager for client.
+ */
+ KeyStoresFactory getClientKeyStoresFactory() throws CertificateException;
}
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 a55db9a427..752113f7b3 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.concurrent.locks.ReentrantLock;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
@@ -58,6 +59,7 @@ import
org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRe
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator;
import org.apache.hadoop.hdds.security.x509.keys.KeyCodec;
+import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil;
import org.apache.hadoop.ozone.OzoneSecurityUtil;
import com.google.common.base.Preconditions;
@@ -109,6 +111,8 @@ public abstract class DefaultCertificateClient implements
CertificateClient {
private String component;
private List<String> pemEncodedCACerts = null;
private final Lock lock;
+ private KeyStoresFactory serverKeyStoresFactory;
+ private KeyStoresFactory clientKeyStoresFactory;
DefaultCertificateClient(SecurityConfig securityConfig, Logger log,
String certSerialId, String component) {
@@ -1060,4 +1064,35 @@ public abstract class DefaultCertificateClient
implements CertificateClient {
public void setLocalCrlId(long crlId) {
this.localCrlId = crlId;
}
+
+ @Override
+ public synchronized KeyStoresFactory getServerKeyStoresFactory()
+ throws CertificateException {
+ if (serverKeyStoresFactory == null) {
+ serverKeyStoresFactory = SecurityUtil.getServerKeyStoresFactory(
+ securityConfig, this, true);
+ }
+ return serverKeyStoresFactory;
+ }
+
+ @Override
+ public KeyStoresFactory getClientKeyStoresFactory()
+ throws CertificateException {
+ if (clientKeyStoresFactory == null) {
+ clientKeyStoresFactory = SecurityUtil.getClientKeyStoresFactory(
+ securityConfig, this, true);
+ }
+ return clientKeyStoresFactory;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (serverKeyStoresFactory != null) {
+ serverKeyStoresFactory.destroy();
+ }
+
+ if (clientKeyStoresFactory != null) {
+ clientKeyStoresFactory.destroy();
+ }
+ }
}
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
index 6147d3a990..5085b6f42e 100644
---
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
@@ -18,6 +18,8 @@
*/
package org.apache.hadoop.hdds.security.x509.keys;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@@ -26,8 +28,13 @@ import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
+
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
+import org.apache.hadoop.hdds.security.ssl.PemFileBasedKeyStoresFactory;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
+import org.apache.hadoop.security.ssl.SSLFactory;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
@@ -135,4 +142,32 @@ public final class SecurityUtil {
return key;
}
+ public static KeyStoresFactory getServerKeyStoresFactory(
+ SecurityConfig securityConfig, CertificateClient client,
+ boolean requireClientAuth) throws CertificateException {
+ PemFileBasedKeyStoresFactory factory =
+ new PemFileBasedKeyStoresFactory(securityConfig, client);
+ try {
+ factory.init(SSLFactory.Mode.SERVER, requireClientAuth);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new CertificateException("Failed to init keyStoresFactory", e,
+ CertificateException.ErrorCode.KEYSTORE_ERROR);
+ }
+ return factory;
+ }
+
+ public static KeyStoresFactory getClientKeyStoresFactory(
+ SecurityConfig securityConfig, CertificateClient client,
+ boolean requireClientAuth) throws CertificateException {
+ PemFileBasedKeyStoresFactory factory =
+ new PemFileBasedKeyStoresFactory(securityConfig, client);
+
+ try {
+ factory.init(SSLFactory.Mode.CLIENT, requireClientAuth);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new CertificateException("Failed to init keyStoresFactory", e,
+ CertificateException.ErrorCode.KEYSTORE_ERROR);
+ }
+ return factory;
+ }
}
diff --git
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java
index 5003bf11c5..f1bbaff47b 100644
---
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java
+++
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java
@@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.List;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
import
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import
org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
@@ -217,6 +218,18 @@ public class CertificateClientTest implements
CertificateClient {
return false;
}
+ @Override
+ public KeyStoresFactory getServerKeyStoresFactory()
+ throws CertificateException {
+ return null;
+ }
+
+ @Override
+ public KeyStoresFactory getClientKeyStoresFactory()
+ throws CertificateException {
+ return null;
+ }
+
@Override
public boolean isCertificateRenewed() {
return isKeyRenewed;
@@ -231,4 +244,8 @@ public class CertificateClientTest implements
CertificateClient {
x509Certificate = newCert;
isKeyRenewed = true;
}
+
+ @Override
+ public void close() throws IOException {
+ }
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java
index 253d45355a..87221827e2 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java
@@ -25,11 +25,16 @@ import java.security.cert.CertStore;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import
org.apache.hadoop.hdds.security.x509.certificate.authority.DefaultApprover;
+import
org.apache.hadoop.hdds.security.x509.certificate.authority.PKIProfiles.DefaultProfile;
import
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import
org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest;
import
org.apache.hadoop.hdds.security.x509.certificates.utils.SelfSignedCertificate;
@@ -37,11 +42,14 @@ import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator;
+import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DEFAULT_DURATION;
import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DEFAULT_DURATION_DEFAULT;
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_MAX_DURATION;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_MAX_DURATION_DEFAULT;
/**
* Test implementation for CertificateClient. To be used only for test
@@ -50,10 +58,17 @@ import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DEFAULT_DURATION_D
public class CertificateClientTestImpl implements CertificateClient {
- private final SecurityConfig securityConfig;
- private final KeyPair keyPair;
private final OzoneConfiguration config;
- private final X509Certificate x509Certificate;
+ private final SecurityConfig securityConfig;
+ private KeyPair keyPair;
+ private X509Certificate x509Certificate;
+ private final KeyPair rootKeyPair;
+ private final X509Certificate rootCert;
+ private HDDSKeyGenerator keyGen;
+ private DefaultApprover approver;
+ private KeyStoresFactory serverKeyStoresFactory;
+ private KeyStoresFactory clientKeyStoresFactory;
+ private boolean isKeyRenewed = false;
public CertificateClientTestImpl(OzoneConfiguration conf) throws Exception {
this(conf, true);
@@ -62,32 +77,59 @@ public class CertificateClientTestImpl implements
CertificateClient {
public CertificateClientTestImpl(OzoneConfiguration conf, boolean rootCA)
throws Exception {
securityConfig = new SecurityConfig(conf);
- HDDSKeyGenerator keyGen =
- new HDDSKeyGenerator(securityConfig.getConfiguration());
+ keyGen = new HDDSKeyGenerator(securityConfig.getConfiguration());
keyPair = keyGen.generateKey();
+ rootKeyPair = keyGen.generateKey();
config = conf;
LocalDateTime start = LocalDateTime.now();
- String certDurationString = conf.get(HDDS_X509_DEFAULT_DURATION,
- HDDS_X509_DEFAULT_DURATION_DEFAULT);
- Duration certDuration = Duration.parse(certDurationString);
- LocalDateTime end = start.plus(certDuration);
+ String rootCACertDuration = conf.get(HDDS_X509_MAX_DURATION,
+ HDDS_X509_MAX_DURATION_DEFAULT);
+ LocalDateTime end = start.plus(Duration.parse(rootCACertDuration));
+ // Generate RootCA certificate
SelfSignedCertificate.Builder builder =
SelfSignedCertificate.newBuilder()
.setBeginDate(start)
.setEndDate(end)
.setClusterID("cluster1")
- .setKey(keyPair)
- .setSubject("localhost")
+ .setKey(rootKeyPair)
+ .setSubject("rootCA@localhost")
.setConfiguration(config)
- .setScmID("TestScmId1");
- if (rootCA) {
- builder.makeCA();
- }
- X509CertificateHolder certificateHolder = null;
- certificateHolder = builder.build();
- x509Certificate = new JcaX509CertificateConverter().getCertificate(
- certificateHolder);
+ .setScmID("scm1")
+ .makeCA();
+ rootCert = new JcaX509CertificateConverter().getCertificate(
+ builder.build());
+
+ // Generate normal certificate, signed by RootCA certificate
+ approver = new DefaultApprover(new DefaultProfile(), securityConfig);
+
+ CertificateSignRequest.Builder csrBuilder = getCSRBuilder();
+ // Get host name.
+ csrBuilder.setKey(keyPair)
+ .setConfiguration(config)
+ .setScmID("scm1")
+ .setClusterID("cluster1")
+ .setSubject("localhost")
+ .setDigitalSignature(true)
+ .setDigitalEncryption(true);
+
+ start = LocalDateTime.now();
+ String certDuration = conf.get(HDDS_X509_DEFAULT_DURATION,
+ HDDS_X509_DEFAULT_DURATION_DEFAULT);
+ X509CertificateHolder certificateHolder =
+ approver.sign(securityConfig, rootKeyPair.getPrivate(),
+ new X509CertificateHolder(rootCert.getEncoded()),
+ Date.from(start.atZone(ZoneId.systemDefault()).toInstant()),
+ Date.from(start.plus(Duration.parse(certDuration))
+ .atZone(ZoneId.systemDefault()).toInstant()),
+ csrBuilder.build(), "scm1", "cluster1");
+ x509Certificate =
+ new JcaX509CertificateConverter().getCertificate(certificateHolder);
+
+ serverKeyStoresFactory = SecurityUtil.getServerKeyStoresFactory(
+ securityConfig, this, true);
+ clientKeyStoresFactory = SecurityUtil.getClientKeyStoresFactory(
+ securityConfig, this, true);
}
@Override
@@ -119,7 +161,7 @@ public class CertificateClientTestImpl implements
CertificateClient {
@Override
public X509Certificate getCACertificate() {
- return x509Certificate;
+ return rootCert;
}
@Override
@@ -255,4 +297,58 @@ public class CertificateClientTestImpl implements
CertificateClient {
return false;
}
+ public boolean isCertificateRenewed() {
+ return isKeyRenewed;
+ }
+
+ public void renewKey() throws Exception {
+ KeyPair newKeyPair = keyGen.generateKey();
+ CertificateSignRequest.Builder csrBuilder = getCSRBuilder();
+ // Get host name.
+ csrBuilder.setKey(newKeyPair)
+ .setConfiguration(config)
+ .setScmID("scm1")
+ .setClusterID("cluster1")
+ .setSubject("localhost")
+ .setDigitalSignature(true);
+
+ String certDuration = config.get(HDDS_X509_DEFAULT_DURATION,
+ HDDS_X509_DEFAULT_DURATION_DEFAULT);
+ Date start = new Date();
+ X509CertificateHolder certificateHolder =
+ approver.sign(securityConfig, rootKeyPair.getPrivate(),
+ new X509CertificateHolder(rootCert.getEncoded()), start,
+ new Date(start.getTime() +
Duration.parse(certDuration).toMillis()),
+ csrBuilder.build(), "scm1", "cluster1");
+ X509Certificate newX509Certificate =
+ new JcaX509CertificateConverter().getCertificate(certificateHolder);
+
+ // Save the new private key and certificate to file
+ // Save certificate and private key to keyStore
+ keyPair = newKeyPair;
+ x509Certificate = newX509Certificate;
+ isKeyRenewed = true;
+ System.out.println(new Date() + " certificated is renewed");
+ }
+
+ @Override
+ public KeyStoresFactory getServerKeyStoresFactory() {
+ return serverKeyStoresFactory;
+ }
+
+ @Override
+ public KeyStoresFactory getClientKeyStoresFactory() {
+ return clientKeyStoresFactory;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (serverKeyStoresFactory != null) {
+ serverKeyStoresFactory.destroy();
+ }
+
+ if (clientKeyStoresFactory != null) {
+ clientKeyStoresFactory.destroy();
+ }
+ }
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainerWithTLS.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainerWithTLS.java
index f410f4d7fe..5bd5fa4a33 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainerWithTLS.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestOzoneContainerWithTLS.java
@@ -23,6 +23,8 @@ import org.apache.hadoop.hdds.HddsConfigKeys;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.MockDatanodeDetails;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
+import
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandRequestProto;
+import
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerCommandResponseProto;
import org.apache.hadoop.hdds.scm.container.ContainerID;
import org.apache.hadoop.hdds.scm.pipeline.MockPipeline;
import org.apache.hadoop.hdds.security.token.ContainerTokenIdentifier;
@@ -37,6 +39,7 @@ import org.apache.hadoop.hdds.scm.XceiverClientSpi;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
import
org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine;
import org.apache.hadoop.ozone.container.common.statemachine.StateContext;
+import org.apache.hadoop.ozone.container.replication.SimpleContainerDownloader;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.Token;
import org.apache.ozone.test.GenericTestUtils;
@@ -53,16 +56,22 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.nio.file.Path;
import java.security.cert.CertificateExpiredException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_KEY_DIR_NAME;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_KEY_DIR_NAME_DEFAULT;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_KEY_LEN;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECURITY_SSL_KEYSTORE_RELOAD_INTERVAL;
+import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECURITY_SSL_TRUSTSTORE_RELOAD_INTERVAL;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DEFAULT_DURATION;
import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS;
import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_KEY;
@@ -120,17 +129,18 @@ public class TestOzoneContainerWithTLS {
conf.setBoolean(HddsConfigKeys.HDDS_GRPC_TLS_TEST_CERT, true);
conf.setInt(HDDS_KEY_LEN, 1024);
- conf.set(HDDS_X509_DEFAULT_DURATION, "PT5S"); // 5s
+ // certificate lives for 5s
+ conf.set(HDDS_X509_DEFAULT_DURATION, "PT5S");
+ conf.set(HDDS_SECURITY_SSL_KEYSTORE_RELOAD_INTERVAL, "1s");
+ conf.set(HDDS_SECURITY_SSL_TRUSTSTORE_RELOAD_INTERVAL, "1s");
long expiryTime = conf.getTimeDuration(
- HddsConfigKeys.HDDS_BLOCK_TOKEN_EXPIRY_TIME,
- HddsConfigKeys.HDDS_BLOCK_TOKEN_EXPIRY_TIME_DEFAULT,
+ HddsConfigKeys.HDDS_BLOCK_TOKEN_EXPIRY_TIME, "1s",
TimeUnit.MILLISECONDS);
caClient = new CertificateClientTestImpl(conf, false);
secretManager = new ContainerTokenSecretManager(new SecurityConfig(conf),
- expiryTime, caClient.getCertificate().
- getSerialNumber().toString());
+ expiryTime, caClient.getCertificate().getSerialNumber().toString());
}
@Test(expected = CertificateExpiredException.class)
@@ -170,13 +180,13 @@ public class TestOzoneContainerWithTLS {
if (containerTokenEnabled) {
secretManager.start(caClient);
client.connect();
- createSecureContainerForTesting(client, containerId,
+ createSecureContainer(client, containerId,
secretManager.generateToken(
UserGroupInformation.getCurrentUser().getUserName(),
ContainerID.valueOf(containerId)));
} else {
client.connect();
- createContainerForTesting(client, containerId);
+ createContainer(client, containerId);
}
} finally {
if (container != null) {
@@ -185,27 +195,170 @@ public class TestOzoneContainerWithTLS {
}
}
- public static void createContainerForTesting(XceiverClientSpi client,
+ @Test
+ public void testContainerDownload() throws Exception {
+ DatanodeDetails dn = MockDatanodeDetails.createDatanodeDetails(
+ UUID.randomUUID().toString(), "localhost", "0.0.0.0",
+ "/default-rack");
+ Pipeline pipeline = MockPipeline.createSingleNodePipeline();
+ conf.set(HDDS_DATANODE_DIR_KEY, tempFolder.newFolder().getPath());
+ conf.setInt(OzoneConfigKeys.DFS_CONTAINER_IPC_PORT,
+ pipeline.getFirstNode().getPort(DatanodeDetails.Port.Name.STANDALONE)
+ .getValue());
+ conf.setBoolean(OzoneConfigKeys.DFS_CONTAINER_IPC_RANDOM_PORT, false);
+
+ OzoneContainer container = null;
+ try {
+ container = new OzoneContainer(dn, conf, getContext(dn), caClient);
+
+ // Set scmId and manually start ozone container.
+ container.start(UUID.randomUUID().toString());
+
+ if (containerTokenEnabled) {
+ secretManager.start(caClient);
+ }
+
+ // Create containers
+ long containerId = ContainerTestHelper.getTestContainerID();
+ int count = 5;
+ List<Long> containerIdList = new ArrayList<>();
+ XceiverClientGrpc client = new XceiverClientGrpc(pipeline, conf,
+ Collections.singletonList(caClient.getCACertificate()));
+ client.connect();
+ for (int i = 0; i < count; i++, containerId++) {
+ if (containerTokenEnabled) {
+ Token<ContainerTokenIdentifier> token = secretManager.generateToken(
+ UserGroupInformation.getCurrentUser().getUserName(),
+ ContainerID.valueOf(containerId));
+ createSecureContainer(client, containerId, token);
+ closeSecureContainer(client, containerId, token);
+ } else {
+ createContainer(client, containerId);
+ closeContainer(client, containerId);
+ }
+ containerIdList.add(containerId);
+ }
+
+ // Wait certificate to expire
+ GenericTestUtils.waitFor(() ->
+ caClient.getCertificate().getNotAfter().before(new Date()),
+ 500, 5000);
+
+ List<DatanodeDetails> sourceDatanodes = new ArrayList<>();
+ sourceDatanodes.add(dn);
+ if (containerTokenEnabled) {
+ // old client still function well after certificate expired
+ Token<ContainerTokenIdentifier> token = secretManager.generateToken(
+ UserGroupInformation.getCurrentUser().getUserName(),
+ ContainerID.valueOf(containerId));
+ createSecureContainer(client, containerId, token);
+ closeSecureContainer(client, containerId++, token);
+ } else {
+ createContainer(client, containerId);
+ closeContainer(client, containerId++);
+ }
+
+ // Download newly created container will fail because of cert expired
+ GenericTestUtils.LogCapturer logCapture = GenericTestUtils.LogCapturer
+ .captureLogs(SimpleContainerDownloader.LOG);
+ SimpleContainerDownloader downloader =
+ new SimpleContainerDownloader(conf, caClient);
+ Path file = downloader.getContainerDataFromReplicas(
+ containerId, sourceDatanodes);
+ downloader.close();
+ Assert.assertNull(file);
+ Assert.assertTrue(logCapture.getOutput().contains(
+ "java.security.cert.CertificateExpiredException"));
+
+ // Renew the certificate
+ caClient.renewKey();
+
+ // old client still function well after certificate renewed
+ if (containerTokenEnabled) {
+ Token<ContainerTokenIdentifier> token = secretManager.generateToken(
+ UserGroupInformation.getCurrentUser().getUserName(),
+ ContainerID.valueOf(containerId));
+ createSecureContainer(client, containerId, token);
+ closeSecureContainer(client, containerId++, token);
+ }
+
+ // Wait keyManager and trustManager to reload
+ Thread.sleep(2000);
+
+ // old client still function well after certificate reload
+ if (containerTokenEnabled) {
+ Token<ContainerTokenIdentifier> token = secretManager.generateToken(
+ UserGroupInformation.getCurrentUser().getUserName(),
+ ContainerID.valueOf(containerId));
+ createSecureContainer(client, containerId, token);
+ closeSecureContainer(client, containerId++, token);
+ } else {
+ createContainer(client, containerId);
+ closeContainer(client, containerId++);
+ }
+
+ // Download container should succeed after key and cert renewed
+ for (Long cId : containerIdList) {
+ downloader = new SimpleContainerDownloader(conf, caClient);
+ try {
+ file = downloader.getContainerDataFromReplicas(cId, sourceDatanodes);
+ downloader.close();
+ Assert.assertNotNull(file);
+ } finally {
+ if (downloader != null) {
+ downloader.close();
+ }
+ client.close();
+ }
+ }
+ } finally {
+ if (container != null) {
+ container.stop();
+ }
+ }
+ }
+
+ public static void createContainer(XceiverClientSpi client,
long containerID) throws Exception {
- ContainerProtos.ContainerCommandRequestProto request =
- ContainerTestHelper.getCreateContainerRequest(
- containerID, client.getPipeline());
- ContainerProtos.ContainerCommandResponseProto response =
- client.sendCommand(request);
+ ContainerCommandRequestProto request = ContainerTestHelper
+ .getCreateContainerRequest(containerID, client.getPipeline());
+ ContainerCommandResponseProto response = client.sendCommand(request);
Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResult() == ContainerProtos.Result.SUCCESS);
}
- public static void createSecureContainerForTesting(XceiverClientSpi client,
+ public static void createSecureContainer(XceiverClientSpi client,
long containerID, Token<ContainerTokenIdentifier> token)
throws Exception {
- ContainerProtos.ContainerCommandRequestProto request =
+ ContainerCommandRequestProto request =
ContainerTestHelper.getCreateContainerSecureRequest(
containerID, client.getPipeline(), token);
- ContainerProtos.ContainerCommandResponseProto response =
+ ContainerCommandResponseProto response =
client.sendCommand(request);
Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResult() == ContainerProtos.Result.SUCCESS);
+ }
+
+ public static void closeContainer(XceiverClientSpi client,
+ long containerID) throws Exception {
+ ContainerCommandRequestProto request = ContainerTestHelper
+ .getCloseContainer(client.getPipeline(), containerID);
+ ContainerCommandResponseProto response = client.sendCommand(request);
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResult() == ContainerProtos.Result.SUCCESS);
}
+ public static void closeSecureContainer(XceiverClientSpi client,
+ long containerID, Token<ContainerTokenIdentifier> token)
+ throws Exception {
+ ContainerCommandRequestProto request =
+ ContainerTestHelper.getCloseContainer(client.getPipeline(),
+ containerID, token);
+ ContainerCommandResponseProto response =
+ client.sendCommand(request);
+ Assert.assertNotNull(response);
+ Assert.assertTrue(response.getResult() == ContainerProtos.Result.SUCCESS);
+ }
private StateContext getContext(DatanodeDetails datanodeDetails) {
DatanodeStateMachine stateMachine = Mockito.mock(
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]