This is an automated email from the ASF dual-hosted git repository.

sshenoy 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 53dd35320e HDDS-8659. Ozone admin SCM CLI command for block tokens. 
(#4747)
53dd35320e is described below

commit 53dd35320e008fdf8277b442c710e02aace988e7
Author: tanvipenumudy <[email protected]>
AuthorDate: Thu Jul 13 11:11:06 2023 +0530

    HDDS-8659. Ozone admin SCM CLI command for block tokens. (#4747)
---
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |   9 ++
 .../hadoop/hdds/protocol/SecretKeyProtocolScm.java |  11 +++
 .../SecretKeyProtocolClientSideTranslatorPB.java   |  17 +++-
 .../hdds/security/symmetric/SecretKeyManager.java  |  13 ++-
 .../apache/hadoop/hdds/utils/HddsServerUtil.java   |  21 +++++
 .../security/symmetric/SecretKeyManagerTest.java   |   2 +-
 .../src/main/proto/ScmSecretKeyProtocol.proto      |  13 +++
 .../SecretKeyProtocolServerSideTranslatorPB.java   |  19 +++-
 .../hdds/scm/security/SecretKeyManagerService.java |   2 +-
 .../hdds/scm/server/SCMSecurityProtocolServer.java |  16 +++-
 .../hdds/scm/cli/ContainerOperationClient.java     |  16 +++-
 .../org/apache/hadoop/hdds/scm/cli/ScmOption.java  |   4 +-
 .../hadoop/ozone/insight/BaseInsightPoint.java     |   4 +-
 .../apache/hadoop/ozone/TestBlockTokensCLI.java    | 105 ++++++++++++++++++++-
 .../ozone/admin/scm/RotateKeySubCommand.java       |  63 +++++++++++++
 .../apache/hadoop/ozone/admin/scm/ScmAdmin.java    |   3 +-
 16 files changed, 301 insertions(+), 17 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
index 1dffca6d57..179bf489cb 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
@@ -362,6 +362,15 @@ public interface ScmClient extends Closeable {
    */
   List<String> getScmRatisRoles() throws IOException;
 
+  /**
+   * Force generates new secret keys (rotate).
+   *
+   * @param force boolean flag that forcefully rotates the key on demand
+   * @return
+   * @throws IOException
+   */
+  boolean rotateSecretKeys(boolean force) throws IOException;
+
   /**
    * Transfer the raft leadership.
    *
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SecretKeyProtocolScm.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SecretKeyProtocolScm.java
index 9439c2ede0..51c1b7b8a0 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SecretKeyProtocolScm.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SecretKeyProtocolScm.java
@@ -18,6 +18,8 @@ package org.apache.hadoop.hdds.protocol;
 
 import org.apache.hadoop.security.KerberosInfo;
 
+import java.io.IOException;
+
 import static 
org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBEROS_PRINCIPAL_KEY;
 
 /**
@@ -28,4 +30,13 @@ import static 
org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBER
     clientPrincipal = HDDS_SCM_KERBEROS_PRINCIPAL_KEY
 )
 public interface SecretKeyProtocolScm extends SecretKeyProtocol {
+
+  /**
+   * Force generates new secret keys (rotate).
+   *
+   * @param force boolean flag that forcefully rotates the key on demand
+   * @return key rotation status
+   * @throws IOException
+   */
+  boolean checkAndRotate(boolean force) throws IOException;
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SecretKeyProtocolClientSideTranslatorPB.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SecretKeyProtocolClientSideTranslatorPB.java
index a0206555e3..64badeb634 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SecretKeyProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SecretKeyProtocolClientSideTranslatorPB.java
@@ -20,6 +20,7 @@ import com.google.common.base.Preconditions;
 import com.google.protobuf.RpcController;
 import com.google.protobuf.ServiceException;
 import org.apache.hadoop.hdds.protocol.SecretKeyProtocol;
+import org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos.SCMGetSecretKeyRequest;
@@ -50,7 +51,7 @@ import java.util.stream.Collectors;
  * {@link SecretKeyProtocol} to the server proxy.
  */
 public class SecretKeyProtocolClientSideTranslatorPB implements
-    SecretKeyProtocol, ProtocolTranslator, Closeable {
+    SecretKeyProtocol, SecretKeyProtocolScm, ProtocolTranslator, Closeable {
 
   /**
    * RpcController is not used and hence is set to null.
@@ -129,6 +130,20 @@ public class SecretKeyProtocolClientSideTranslatorPB 
implements
     return ManagedSecretKey.fromProtobuf(secretKeyProto);
   }
 
+  @Override
+  public boolean checkAndRotate(boolean force) throws IOException {
+    SCMSecretKeyProtocolProtos.SCMGetCheckAndRotateRequest request =
+        SCMSecretKeyProtocolProtos.SCMGetCheckAndRotateRequest.newBuilder()
+            .setForce(force)
+            .build();
+    boolean checkAndRotateStatus =
+        submitRequest(Type.CheckAndRotate, builder ->
+            builder.setCheckAndRotateRequest(request))
+            .getCheckAndRotateResponseProto().getStatus();
+
+    return checkAndRotateStatus;
+  }
+
   @Override
   public ManagedSecretKey getSecretKey(UUID id) throws IOException {
     SCMGetSecretKeyRequest request = SCMGetSecretKeyRequest.newBuilder()
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManager.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManager.java
index 227317f913..20432f2a6a 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManager.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManager.java
@@ -109,23 +109,30 @@ public class SecretKeyManager implements SecretKeyClient {
    *
    * @return true if rotation actually happens, false if it doesn't.
    */
-  public synchronized boolean checkAndRotate() throws SCMException {
+  public synchronized boolean checkAndRotate(boolean force)
+      throws SCMException {
     // Initialize the state if it's not initialized already.
     checkAndInitialize();
 
     ManagedSecretKey currentKey = state.getCurrentKey();
-    if (shouldRotate(currentKey)) {
+    if (force || shouldRotate(currentKey)) {
       ManagedSecretKey newCurrentKey = generateSecretKey();
       List<ManagedSecretKey> updatedKeys = state.getSortedKeys()
           .stream().filter(x -> !x.isExpired())
           .collect(toList());
       updatedKeys.add(newCurrentKey);
 
-      LOG.info("SecretKey rotation is happening, new key generated {}",
+      LOG.info((force ? "Forced " : "") +
+              "SecretKey rotation is happening, new key generated {}",
           newCurrentKey);
       state.updateKeys(updatedKeys);
       return true;
     }
+    if (LOG.isDebugEnabled()) {
+      LOG.debug(
+          "The latest key was created at: " + currentKey.getCreationTime() +
+              " which does not pass the rotation duration");
+    }
     return false;
   }
 
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
index 2c59bc1e3c..70d394e73b 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HddsServerUtil.java
@@ -44,6 +44,7 @@ import org.apache.hadoop.hdds.DFSConfigKeysLegacy;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
+import org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm;
 import 
org.apache.hadoop.hdds.protocolPB.SecretKeyProtocolClientSideTranslatorPB;
 import org.apache.hadoop.hdds.protocolPB.SecretKeyProtocolDatanodePB;
 import org.apache.hadoop.hdds.protocolPB.SecretKeyProtocolOmPB;
@@ -488,6 +489,26 @@ public final class HddsServerUtil {
         SCMSecurityProtocolClientSideTranslatorPB.class, conf);
   }
 
+  /**
+   * Creates a {@link org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm}
+   * intended to be used by clients under the SCM identity.
+   *
+   * @param conf - Ozone configuration
+   * @return {@link org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm}
+   * @throws IOException
+   */
+  public static SecretKeyProtocolScm getSecretKeyClientForSCM(
+      ConfigurationSource conf) throws IOException {
+    SecretKeyProtocolClientSideTranslatorPB scmSecretClient =
+        new SecretKeyProtocolClientSideTranslatorPB(
+            new SecretKeyProtocolFailoverProxyProvider(conf,
+                UserGroupInformation.getCurrentUser(),
+                SecretKeyProtocolScmPB.class), SecretKeyProtocolScmPB.class);
+
+    return TracingUtil.createProxy(scmSecretClient,
+        SecretKeyProtocolScm.class, conf);
+  }
+
   /**
    * Create a {@link org.apache.hadoop.hdds.protocol.SecretKeyProtocol} for
    * datanode service, should be use only if user is the Datanode identity.
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManagerTest.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManagerTest.java
index 675488f5e6..6f1a43e948 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManagerTest.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyManagerTest.java
@@ -162,7 +162,7 @@ public class SecretKeyManagerTest {
     ManagedSecretKey initialCurrentKey = state.getCurrentKey();
     Mockito.reset(mockedKeyStore);
 
-    assertEquals(expectRotate, lifeCycleManager.checkAndRotate());
+    assertEquals(expectRotate, lifeCycleManager.checkAndRotate(false));
 
     if (expectRotate) {
       // Verify rotation behavior.
diff --git 
a/hadoop-hdds/interface-server/src/main/proto/ScmSecretKeyProtocol.proto 
b/hadoop-hdds/interface-server/src/main/proto/ScmSecretKeyProtocol.proto
index 88b00ff7c3..010e38ee99 100644
--- a/hadoop-hdds/interface-server/src/main/proto/ScmSecretKeyProtocol.proto
+++ b/hadoop-hdds/interface-server/src/main/proto/ScmSecretKeyProtocol.proto
@@ -46,6 +46,8 @@ message SCMSecretKeyRequest {
     optional string traceID = 2;
 
     optional SCMGetSecretKeyRequest getSecretKeyRequest = 3;
+
+    optional SCMGetCheckAndRotateRequest checkAndRotateRequest = 4;
 }
 
 message SCMSecretKeyResponse {
@@ -67,12 +69,15 @@ message SCMSecretKeyResponse {
 
     optional SCMSecretKeysListResponse secretKeysListResponseProto = 13;
 
+    optional SCMGetCheckAndRotateResponse checkAndRotateResponseProto = 14;
+
 }
 
 enum Type {
     GetCurrentSecretKey = 1;
     GetSecretKey = 2;
     GetAllSecretKeys = 3;
+    CheckAndRotate = 4;
 }
 
 enum Status {
@@ -98,6 +103,10 @@ message SCMGetSecretKeyRequest {
     required UUID secretKeyId = 1;
 }
 
+message SCMGetCheckAndRotateRequest {
+    optional bool force = 1 [default = false];
+}
+
 message SCMGetCurrentSecretKeyResponse {
     required ManagedSecretKey secretKey = 1;
 }
@@ -109,3 +118,7 @@ message SCMGetSecretKeyResponse {
 message SCMSecretKeysListResponse {
     repeated ManagedSecretKey secretKeys = 1;
 }
+
+message SCMGetCheckAndRotateResponse {
+    optional bool status = 1;
+}
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SecretKeyProtocolServerSideTranslatorPB.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SecretKeyProtocolServerSideTranslatorPB.java
index 527d7a42fa..e08d2be5f3 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SecretKeyProtocolServerSideTranslatorPB.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SecretKeyProtocolServerSideTranslatorPB.java
@@ -19,7 +19,8 @@ package org.apache.hadoop.hdds.scm.protocol;
 import com.google.protobuf.ProtocolMessageEnum;
 import com.google.protobuf.RpcController;
 import com.google.protobuf.ServiceException;
-import org.apache.hadoop.hdds.protocol.SecretKeyProtocol;
+import org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm;
+import 
org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos.SCMGetCheckAndRotateResponse;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos.SCMGetCurrentSecretKeyResponse;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos.SCMGetSecretKeyRequest;
 import 
org.apache.hadoop.hdds.protocol.proto.SCMSecretKeyProtocolProtos.SCMGetSecretKeyResponse;
@@ -52,13 +53,13 @@ public class SecretKeyProtocolServerSideTranslatorPB
   private static final Logger LOG =
       LoggerFactory.getLogger(SecretKeyProtocolServerSideTranslatorPB.class);
 
-  private final SecretKeyProtocol impl;
+  private final SecretKeyProtocolScm impl;
   private final StorageContainerManager scm;
 
   private OzoneProtocolMessageDispatcher<SCMSecretKeyRequest,
       SCMSecretKeyResponse, ProtocolMessageEnum> dispatcher;
 
-  public SecretKeyProtocolServerSideTranslatorPB(SecretKeyProtocol impl,
+  public SecretKeyProtocolServerSideTranslatorPB(SecretKeyProtocolScm impl,
       StorageContainerManager storageContainerManager,
       ProtocolMessageMetrics messageMetrics) {
     this.impl = impl;
@@ -102,6 +103,12 @@ public class SecretKeyProtocolServerSideTranslatorPB
             .setSecretKeysListResponseProto(getAllSecretKeys())
             .build();
 
+      case CheckAndRotate:
+        return scmSecurityResponse
+            .setCheckAndRotateResponseProto(
+                checkAndRotate(request.getCheckAndRotateRequest().getForce()))
+            .build();
+
       default:
         throw new IllegalArgumentException(
             "Unknown request type: " + request.getCmdType());
@@ -153,6 +160,12 @@ public class SecretKeyProtocolServerSideTranslatorPB
         .build();
   }
 
+  private SCMGetCheckAndRotateResponse checkAndRotate(boolean force)
+      throws IOException {
+    return SCMGetCheckAndRotateResponse.newBuilder()
+        .setStatus(impl.checkAndRotate(force)).build();
+  }
+
   private Status exceptionToResponseStatus(IOException ex) {
     if (ex instanceof SCMSecretKeyException) {
       return Status.values()[
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/security/SecretKeyManagerService.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/security/SecretKeyManagerService.java
index b81b4eab13..0df2ddef81 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/security/SecretKeyManagerService.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/security/SecretKeyManagerService.java
@@ -126,7 +126,7 @@ public class SecretKeyManagerService implements SCMService, 
Runnable {
     }
 
     try {
-      secretKeyManager.checkAndRotate();
+      secretKeyManager.checkAndRotate(false);
     } catch (Exception e) {
       LOG.error("Error occurred when updating SecretKeys.", e);
     }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
index 6fd44a4c0f..588177a0d7 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java
@@ -39,7 +39,7 @@ import java.util.stream.Collectors;
 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.protocol.SecretKeyProtocol;
+import org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DatanodeDetailsProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeDetailsProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeType;
@@ -98,7 +98,7 @@ import static 
org.apache.hadoop.hdds.security.x509.certificate.utils.Certificate
     serverPrincipal = ScmConfig.ConfigStrings.HDDS_SCM_KERBEROS_PRINCIPAL_KEY)
 @InterfaceAudience.Private
 public class SCMSecurityProtocolServer implements SCMSecurityProtocol,
-    SecretKeyProtocol {
+    SecretKeyProtocolScm {
 
   private static final Logger LOGGER = LoggerFactory
       .getLogger(SCMSecurityProtocolServer.class);
@@ -248,6 +248,18 @@ public class SCMSecurityProtocolServer implements 
SCMSecurityProtocol,
     }
   }
 
+  @Override
+  public boolean checkAndRotate(boolean force) throws SCMSecretKeyException {
+    validateSecretKeyStatus();
+    try {
+      return secretKeyManager.checkAndRotate(force);
+    } catch (SCMException ex) {
+      LOGGER.error("Error rotating secret keys", ex);
+      throw new SCMSecretKeyException(ex.getMessage(),
+          SCMSecretKeyException.ErrorCode.INTERNAL_ERROR);
+    }
+  }
+
   @Override
   public synchronized List<String> getAllRootCaCertificates()
       throws IOException {
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
index e8b657ecb1..e10d1d5b83 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
@@ -23,6 +23,7 @@ import org.apache.hadoop.conf.StorageUnit;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.SecretKeyProtocolScm;
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto;
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ReadContainerResponseProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -43,6 +44,7 @@ import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol;
 import org.apache.hadoop.hdds.scm.storage.ContainerProtocolCalls;
 import org.apache.hadoop.hdds.utils.HAUtils;
+import org.apache.hadoop.hdds.utils.HddsServerUtil;
 import org.apache.hadoop.ozone.ClientVersion;
 import org.apache.hadoop.ozone.OzoneSecurityUtil;
 import org.apache.hadoop.ozone.upgrade.UpgradeFinalizer.StatusAndMessages;
@@ -73,6 +75,7 @@ public class ContainerOperationClient implements ScmClient {
   private final HddsProtos.ReplicationType replicationType;
   private final StorageContainerLocationProtocol
       storageContainerLocationClient;
+  private final SecretKeyProtocolScm secretKeyClient;
   private final boolean containerTokenEnabled;
   private final OzoneConfiguration configuration;
   private XceiverClientManager xceiverClientManager;
@@ -85,9 +88,10 @@ public class ContainerOperationClient implements ScmClient {
     return xceiverClientManager;
   }
 
-  public ContainerOperationClient(OzoneConfiguration conf) {
+  public ContainerOperationClient(OzoneConfiguration conf) throws IOException {
     this.configuration = conf;
     storageContainerLocationClient = newContainerRpcClient(conf);
+    secretKeyClient = newSecretKeyClient(conf);
     containerSizeB = (int) conf.getStorageSize(OZONE_SCM_CONTAINER_SIZE,
         OZONE_SCM_CONTAINER_SIZE_DEFAULT, StorageUnit.BYTES);
     boolean useRatis = conf.getBoolean(
@@ -124,6 +128,11 @@ public class ContainerOperationClient implements ScmClient 
{
     return HAUtils.getScmContainerClient(configSource);
   }
 
+  public static SecretKeyProtocolScm newSecretKeyClient(
+      ConfigurationSource configSource) throws IOException {
+    return HddsServerUtil.getSecretKeyClientForSCM(configSource);
+  }
+
   @Override
   public ContainerWithPipeline createContainer(String owner)
       throws IOException {
@@ -466,6 +475,11 @@ public class ContainerOperationClient implements ScmClient 
{
     return storageContainerLocationClient.getScmInfo().getRatisPeerRoles();
   }
 
+  @Override
+  public boolean rotateSecretKeys(boolean force) throws IOException {
+    return secretKeyClient.checkAndRotate(force);
+  }
+
   @Override
   public void transferLeadership(String newLeaderId) throws IOException {
     storageContainerLocationClient.transferLeadership(newLeaderId);
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ScmOption.java 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ScmOption.java
index 14744e5f80..dea8ac0ec8 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ScmOption.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ScmOption.java
@@ -52,7 +52,7 @@ public class ScmOption {
       "ServiceId of SCM HA Cluster")
   private String scmServiceId;
 
-  public ScmClient createScmClient() {
+  public ScmClient createScmClient() throws IOException {
     GenericParentCommand parent = (GenericParentCommand)
         spec.root().userObject();
     OzoneConfiguration conf = parent.createOzoneConfiguration();
@@ -61,7 +61,7 @@ public class ScmOption {
     return new ContainerOperationClient(conf);
   }
 
-  public ScmClient createScmClient(OzoneConfiguration conf) {
+  public ScmClient createScmClient(OzoneConfiguration conf) throws IOException 
{
     checkAndSetSCMAddressArg(conf);
     return new ContainerOperationClient(conf);
   }
diff --git 
a/hadoop-ozone/insight/src/main/java/org/apache/hadoop/ozone/insight/BaseInsightPoint.java
 
b/hadoop-ozone/insight/src/main/java/org/apache/hadoop/ozone/insight/BaseInsightPoint.java
index 2a4cf88798..2789d03bfc 100644
--- 
a/hadoop-ozone/insight/src/main/java/org/apache/hadoop/ozone/insight/BaseInsightPoint.java
+++ 
b/hadoop-ozone/insight/src/main/java/org/apache/hadoop/ozone/insight/BaseInsightPoint.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.ozone.insight;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -68,7 +69,8 @@ public abstract class BaseInsightPoint implements 
InsightPoint {
   /**
    * Create scm client.
    */
-  public ScmClient createScmClient(OzoneConfiguration ozoneConf) {
+  public ScmClient createScmClient(OzoneConfiguration ozoneConf)
+      throws IOException {
     if (!HddsUtils.getHostNameFromConfigKeys(ozoneConf,
         ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY).isPresent()) {
 
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokensCLI.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokensCLI.java
index c53ef2ea18..032c3dc779 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokensCLI.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestBlockTokensCLI.java
@@ -17,12 +17,14 @@
  */
 package org.apache.hadoop.ozone;
 
+import com.google.common.collect.Maps;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
 import org.apache.hadoop.hdds.cli.OzoneAdmin;
 import org.apache.hadoop.hdds.conf.DefaultConfigManager;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.ScmConfig;
 import org.apache.hadoop.hdds.scm.server.SCMHTTPServerConfig;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
 import org.apache.hadoop.hdds.security.symmetric.SecretKeyManager;
 import org.apache.hadoop.hdds.utils.IOUtils;
 import org.apache.hadoop.minikdc.MiniKdc;
@@ -35,6 +37,7 @@ import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 import org.junit.rules.Timeout;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,10 +48,15 @@ import java.io.UnsupportedEncodingException;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
 import java.util.Properties;
 import java.util.UUID;
 import java.util.concurrent.TimeoutException;
 
+import static java.time.Duration.between;
 import static 
org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION;
 import static 
org.apache.hadoop.hdds.DFSConfigKeysLegacy.DFS_DATANODE_KERBEROS_KEYTAB_FILE_KEY;
 import static 
org.apache.hadoop.hdds.DFSConfigKeysLegacy.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY;
@@ -59,6 +67,7 @@ import static 
org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBER
 import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CLIENT_ADDRESS_KEY;
 import static 
org.apache.hadoop.hdds.scm.server.SCMHTTPServerConfig.ConfigStrings.HDDS_SCM_HTTP_KERBEROS_KEYTAB_FILE_KEY;
 import static 
org.apache.hadoop.hdds.scm.server.SCMHTTPServerConfig.ConfigStrings.HDDS_SCM_HTTP_KERBEROS_PRINCIPAL_KEY;
+import static 
org.apache.hadoop.hdds.security.symmetric.SecretKeyConfig.parseRotateDuration;
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS;
 import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_ENABLED_KEY;
 import static 
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_HTTP_KERBEROS_KEYTAB_FILE;
@@ -219,6 +228,100 @@ public final class TestBlockTokensCLI {
     return null;
   }
 
+  private boolean isForceFlagPresent(String[] args) {
+    for (String arg : args) {
+      if (arg.equals("--force")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Test
+  public void testRotateKeySCMAdminCommandWithForceFlag()
+      throws InterruptedException, TimeoutException {
+    GenericTestUtils.waitFor(() -> cluster.getScmLeader() != null, 100, 1000);
+    InetSocketAddress address = cluster.getScmLeader().getClientRpcAddress();
+    String hostPort = address.getHostName() + ":" + address.getPort();
+
+    testRotateKeySCMAdminCommandUtil(createArgsForCommand(
+        new String[]{"scm", "rotate", "--scm", hostPort, "--force"}));
+  }
+
+  @Test
+  public void testRotateKeySCMAdminCommandWithoutForceFlag()
+      throws InterruptedException, TimeoutException {
+    GenericTestUtils.waitFor(() -> cluster.getScmLeader() != null, 100, 1000);
+    InetSocketAddress address = cluster.getScmLeader().getClientRpcAddress();
+    String hostPort = address.getHostName() + ":" + address.getPort();
+
+    testRotateKeySCMAdminCommandUtil(
+        createArgsForCommand(new String[]{"scm", "rotate", "--scm", 
hostPort}));
+  }
+
+  public void testRotateKeySCMAdminCommandUtil(String[] args) {
+    // Get the initial secret key.
+    String initialKey =
+        getScmSecretKeyManager().getCurrentSecretKey().toString();
+
+    // Assert that both initial key and subsequent key are the same before
+    // rotating.
+    String currentKey =
+        getScmSecretKeyManager().getCurrentSecretKey().toString();
+    Assertions.assertEquals(initialKey, currentKey);
+
+    // Rotate the secret key.
+    ozoneAdmin.execute(args);
+
+    // Get the new secret key.
+    String newKey =
+        getScmSecretKeyManager().getCurrentSecretKey().toString();
+
+    // Assert that the old key and new key are not the same after rotating if
+    // either:
+    // 1. The rotation duration has surpassed, or
+    // 2. The --force flag has been passed.
+    // Otherwise, both keys should be the same.
+    if (isForceFlagPresent(args) ||
+        shouldRotate(getScmSecretKeyManager().getCurrentSecretKey())) {
+      Assertions.assertNotEquals(initialKey, newKey);
+    } else {
+      Assertions.assertEquals(initialKey, newKey);
+    }
+  }
+
+  private String getSetConfStringFromConf(String key) {
+    return String.format("--set=%s=%s", key, conf.get(key));
+  }
+
+  public boolean shouldRotate(ManagedSecretKey currentKey) {
+    Duration established = between(currentKey.getCreationTime(), 
Instant.now());
+    return established.compareTo(parseRotateDuration(conf)) >= 0;
+  }
+
+  /**
+   * Since ScmAdmin relies on ScmOption to generate configurations, it uses the
+   * default configuration obtained from
+   * {@link org.apache.hadoop.hdds.scm.cli.ScmOption#createScmClient(
+   * OzoneConfiguration)} using createOzoneConfiguration().
+   * In the absence of a better way to pass these configurations via 
integration
+   * tests in the ozone admin shell, this method handles the passing of 
kerberos
+   * and SCM HA configurations by creating strings in the "--set=key=value"
+   * format.
+   */
+  private String[] createArgsForCommand(String[] additionalArgs) {
+    OzoneConfiguration defaultConf = ozoneAdmin.createOzoneConfiguration();
+    Map<String, String> diff = 
Maps.difference(defaultConf.getOzoneProperties(),
+        conf.getOzoneProperties()).entriesOnlyOnRight();
+    String[] args = new String[diff.size() + additionalArgs.length];
+    int i = 0;
+    for (Map.Entry<String, String> entry : diff.entrySet()) {
+      args[i++] = getSetConfStringFromConf(entry.getKey());
+    }
+    System.arraycopy(additionalArgs, 0, args, i, additionalArgs.length);
+    return args;
+  }
+
   private static void startCluster()
       throws IOException, TimeoutException, InterruptedException {
     OzoneManager.setTestSecureOmFlag(true);
@@ -229,7 +332,7 @@ public final class TestBlockTokensCLI {
         .setScmId(scmId)
         .setNumDatanodes(3)
         .setNumOfStorageContainerManagers(3)
-        .setNumOfOzoneManagers(1);
+        .setNumOfOzoneManagers(3);
 
     cluster = (MiniOzoneHAClusterImpl) builder.build();
     cluster.waitForClusterToBeReady();
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/RotateKeySubCommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/RotateKeySubCommand.java
new file mode 100644
index 0000000000..7e6e4104a9
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/RotateKeySubCommand.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ozone.admin.scm;
+
+import java.io.IOException;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
+import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import picocli.CommandLine;
+
+/**
+ * Handler of ozone admin scm rotate command.
+ */
[email protected](
+    name = "rotate",
+    description = "CLI command to force generate new keys (rotate)",
+    mixinStandardHelpOptions = true,
+    versionProvider = HddsVersionProvider.class)
+public class RotateKeySubCommand extends ScmSubcommand {
+
+  @CommandLine.Option(names = "--force",
+      description = "Force generate new keys")
+  private boolean force = false;
+
+  @CommandLine.ParentCommand
+  private ScmAdmin parent;
+
+  @Override
+  protected void execute(ScmClient scmClient) throws IOException {
+    try (ScmClient client = new ContainerOperationClient(
+        parent.getParent().getOzoneConf())) {
+      boolean status = false;
+      try {
+        status = client.rotateSecretKeys(force);
+      } catch (IOException e) {
+        System.err.println("Secret key rotation failed: " + e.getMessage());
+        return;
+      }
+      if (status) {
+        System.out.println("Secret key rotation is complete. A new key has " +
+            "been generated.");
+      }
+    }
+  }
+}
+
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
index fbc5a3b52b..98eba154b2 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
@@ -40,7 +40,8 @@ import picocli.CommandLine.Spec;
         FinalizationScmStatusSubcommand.class,
         TransferScmLeaderSubCommand.class,
         DeletedBlocksTxnCommands.class,
-        DecommissionScmSubcommand.class
+        DecommissionScmSubcommand.class,
+        RotateKeySubCommand.class
     })
 @MetaInfServices(SubcommandWithParent.class)
 public class ScmAdmin extends GenericCli implements SubcommandWithParent {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to