You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by si...@apache.org on 2026/10/17 18:35:10 UTC

(ozone) 12/13: Improve certificate request handling in SCMSecurityProtocolServer and DefaultCAServer.

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

smengcl pushed a commit to branch ozone-2.2
in repository https://gitbox.apache.org/repos/asf/ozone.git

commit 803d09e128860fc03cd4ca931d33b8d330bdc950
Author: Siyao Meng <50...@users.noreply.github.com>
AuthorDate: Fri Oct 16 21:18:49 2026 -0700

    Improve certificate request handling in SCMSecurityProtocolServer and DefaultCAServer.
    
    (cherry picked from commit 7f9b6237deb2b06d73d1ac2b78cccaa60780c3ef)
    
    Conflicts:
            hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
 .../org/apache/hadoop/hdds/recon/ReconConfig.java  |  3 +-
 .../common/src/main/resources/ozone-default.xml    |  6 ++-
 .../certificate/authority/DefaultCAServer.java     | 15 +++++-
 .../authority/profile/DefaultCAProfile.java        | 14 ++++--
 .../authority/profile/DefaultProfile.java          |  2 +-
 .../certificate/authority/TestDefaultCAServer.java |  3 +-
 .../certificate/authority/TestDefaultProfile.java  | 15 ++++++
 .../hdds/scm/server/SCMSecurityProtocolServer.java | 34 ++++++++++++++
 .../apache/hadoop/hdds/scm/TestHddsServerUtil.java | 16 +++++++
 .../hadoop/ozone/TestSecureOzoneCluster.java       | 54 ++++++++++++++++++++++
 10 files changed, 151 insertions(+), 11 deletions(-)

diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfig.java
index de6e0697a9f..78b4d7d4372 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfig.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfig.java
@@ -32,7 +32,8 @@ public class ReconConfig {
       type = ConfigType.STRING,
       defaultValue = "",
       tags = { ConfigTag.SECURITY, ConfigTag.RECON, ConfigTag.OZONE },
-      description = "This Kerberos principal is used by the Recon service."
+      description = "This Kerberos principal is used by the Recon service. SCM issues a Recon certificate only to this"
+          + " service user, so the property must be set in the configuration of SCM as well."
   )
   private String principal;
 
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index eef89592300..38ce72f7707 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -2438,8 +2438,8 @@
     <value>OM/_HOST@REALM</value>
     <tag>OZONE, SECURITY, KERBEROS</tag>
     <description>The OzoneManager service principal. Ex om/_HOST@REALM.COM
-      SCM allocates and deletes blocks only for this service user or an SCM admin, so the property must be set in the
-      configuration of SCM as well.
+      SCM issues an OM certificate only to this service user, and allocates and deletes blocks only for this service
+      user or an SCM admin, so the property must be set in the configuration of SCM as well.
     </description>
   </property>
   <property>
@@ -5005,6 +5005,8 @@
       dn/_HOST@REALM.TLD. Each Datanode will substitute _HOST with its
       own fully qualified hostname at startup. The _HOST placeholder
       allows using the same configuration setting on all Datanodes.
+      SCM issues a Datanode certificate only to this service user, so the
+      property must be set in the configuration of SCM as well.
     </description>
   </property>
   <property>
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
index 9e67e0d0b72..c2ca72ad419 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
@@ -49,6 +49,7 @@
 import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
 import org.apache.hadoop.hdds.security.SecurityConfig;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultProfile;
 import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.PKIProfile;
 import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
 import org.apache.hadoop.hdds.security.x509.certificate.utils.SelfSignedCertificate;
@@ -120,6 +121,7 @@ public class DefaultCAServer implements CertificateServer {
    */
   private PKIProfile profile;
   private CertificateApprover approver;
+  private CertificateApprover leafApprover;
   private CertificateStore store;
   private Lock lock;
   private BigInteger rootCertificateId;
@@ -157,6 +159,7 @@ public void init(SecurityConfig securityConfig, CAType type)
     caRootX509Path = securityConfig.getCertificateLocation(componentName);
     this.config = securityConfig;
     this.approver = new DefaultApprover(profile, this.config);
+    this.leafApprover = new DefaultApprover(new DefaultProfile(), this.config);
 
     /* In future we will split this code to have different kind of CAs.
      * Right now, we have only self-signed CertificateServer.
@@ -210,7 +213,7 @@ public Future<CertPath> requestCertificate(
       String certSerialId) {
     Duration certDuration = getDuration(role);
 
-    CompletableFuture<Void> csrInspection = approver.inspectCSR(csr);
+    CompletableFuture<Void> csrInspection = getApprover(role).inspectCSR(csr);
     CompletableFuture<CertPath> certPathPromise = new CompletableFuture<>();
     // An inspection that has not completed is not an approval.
     if (!csrInspection.isDone()) {
@@ -254,6 +257,14 @@ public Future<CertPath> requestCertificate(
     return certPathPromise;
   }
 
+  /**
+   * Only an SCM can be issued a CA certificate, by this CA whether it is the root CA or the intermediate CA of another
+   * SCM (HDDS-8286). The other roles get a leaf certificate whatever the profile of this CA.
+   */
+  private CertificateApprover getApprover(NodeType role) {
+    return role == NodeType.SCM ? approver : leafApprover;
+  }
+
   private Duration getDuration(NodeType role) {
     // When issuing certificates for sub-ca use the max certificate duration similar to self-signed root certificate.
     if (role == NodeType.SCM) {
@@ -273,7 +284,7 @@ private X509Certificate signAndStoreCertificate(
     X509Certificate xcert;
     try {
       Preconditions.checkState(!Strings.isNullOrEmpty(certSerialId));
-      xcert = approver.sign(config,
+      xcert = getApprover(role).sign(config,
           getPrivateKey(),
           getCACertificate(),
           Date.from(beginDate.toInstant()),
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultCAProfile.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultCAProfile.java
index 5aa87d5aea1..1a38e9c341d 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultCAProfile.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultCAProfile.java
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.hdds.security.x509.certificate.authority.profile;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.function.BiPredicate;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -33,6 +34,14 @@ public class DefaultCAProfile extends DefaultProfile {
   private static final BiPredicate<Extension, PKIProfile>
       VALIDATE_BASIC_CONSTRAINTS = DefaultCAProfile::validateBasicExtensions;
 
+  // Basic constraints are accepted by the CA profile only.
+  private static final Map<ASN1ObjectIdentifier, BiPredicate<Extension, PKIProfile>> CA_EXTENSIONS_MAP =
+      new HashMap<>(EXTENSIONS_MAP);
+
+  static {
+    CA_EXTENSIONS_MAP.put(Extension.basicConstraints, VALIDATE_BASIC_CONSTRAINTS);
+  }
+
   private static boolean validateBasicExtensions(Extension ext,
       PKIProfile pkiProfile) {
     BasicConstraints constraints =
@@ -48,10 +57,7 @@ public boolean isCA() {
   @Override
   public Map<ASN1ObjectIdentifier,
       BiPredicate<Extension, PKIProfile>> getExtensionsMap() {
-    // Add basic constraint.
-    EXTENSIONS_MAP.putIfAbsent(Extension.basicConstraints,
-        VALIDATE_BASIC_CONSTRAINTS);
-    return EXTENSIONS_MAP;
+    return CA_EXTENSIONS_MAP;
   }
 
   @Override
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultProfile.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultProfile.java
index b4c7ad5d4e1..8e2fa736550 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultProfile.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/profile/DefaultProfile.java
@@ -269,7 +269,7 @@ public boolean validateExtension(Extension extension) {
       return false;
     }
 
-    return EXTENSIONS_MAP.getOrDefault(extension.getExtnId(), (x, y) -> false)
+    return getExtensionsMap().getOrDefault(extension.getExtnId(), (x, y) -> false)
         .test(extension, this);
   }
 
diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java
index 77776499841..83989c68c5c 100644
--- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java
+++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java
@@ -266,7 +266,8 @@ public void testBasicConstraintsAllowedForScmRoleOnly() throws Exception {
     ExecutionException execution = assertThrows(ExecutionException.class,
         () -> testCA.requestCertificate(caCsr, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM,
             String.valueOf(System.nanoTime())).get());
-    assertThat(execution.getCause().getMessage()).contains("allowed for the SCM role only, not for OM");
+    // The leaf profile used for other roles rejects the CA constraint before the role check.
+    assertThat(execution.getCause().getMessage()).contains("Failed to verify the CSR");
     assertThat(caStore.getStoredCertificates()).isEmpty();
 
     X509Certificate scmCert = CertificateCodec.firstCertificateFrom(testCA.requestCertificate(caCsr,
diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultProfile.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultProfile.java
index 06ca02857e9..c0ec1d8a88c 100644
--- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultProfile.java
+++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultProfile.java
@@ -34,13 +34,16 @@
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.security.SecurityConfig;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultCAProfile;
 import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultProfile;
 import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest;
 import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator;
+import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.X500NameBuilder;
 import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
 import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.asn1.x509.Extensions;
@@ -352,6 +355,18 @@ private PKCS10CertificationRequest getInvalidCSR(KeyPair kPair,
     return p10Builder.build(signer);
   }
 
+  /**
+   * Basic constraints, which make a certificate a CA, are accepted by the CA profile only.
+   */
+  @Test
+  void testBasicConstraintsOnlyInCAProfile() throws IOException {
+    Extension ca = new Extension(Extension.basicConstraints, true, new DEROctetString(new BasicConstraints(true)));
+
+    assertThat(new DefaultCAProfile().validateExtension(ca)).isTrue();
+    assertThat(defaultProfile.getSupportedExtensions()).doesNotContain(Extension.basicConstraints);
+    assertThat(defaultProfile.validateExtension(ca)).isFalse();
+  }
+
   /**
    * Generate an Extension with rfc822Name.
    * @param extensionCode - Extension Code.
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 f1cc6fb9c27..bed276bc912 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
@@ -17,12 +17,16 @@
 
 package org.apache.hadoop.hdds.scm.server;
 
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_DATANODE_KERBEROS_PRINCIPAL_KEY;
+import static org.apache.hadoop.hdds.recon.ReconConfig.ConfigStrings.OZONE_RECON_KERBEROS_PRINCIPAL_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBEROS_PRINCIPAL_KEY;
 import static org.apache.hadoop.hdds.scm.ScmUtils.checkIfCertSignRequestAllowed;
 import static org.apache.hadoop.hdds.security.exception.SCMSecretKeyException.ErrorCode.SECRET_KEY_NOT_ENABLED;
 import static org.apache.hadoop.hdds.security.exception.SCMSecretKeyException.ErrorCode.SECRET_KEY_NOT_INITIALIZED;
 import static org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode.CERTIFICATE_NOT_FOUND;
 import static org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode.GET_CA_CERT_FAILED;
 import static org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode.GET_CERTIFICATE_FAILED;
+import static org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode.UNABLE_TO_ISSUE_CERTIFICATE;
 import static org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateApprover.ApprovalType.KERBEROS_TRUSTED;
 import static org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec.getPEMEncodedString;
 import static org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest.getCertificationRequest;
@@ -342,6 +346,7 @@ private synchronized String getEncodedCertToString(String certSignReq,
       // SCM role certificates are CA certificates and are issued to SCM admins only, the check addSCM applies.
       storageContainerManager.checkAdminAccess(getRpcRemoteUser(), false);
     }
+    checkRequesterIsService(nodeType);
     Future<CertPath> future;
     PKCS10CertificationRequest csr = getCertificationRequest(certSignReq);
     if (nodeType == NodeType.SCM && rootCertificateServer != null) {
@@ -369,6 +374,35 @@ private synchronized String getEncodedCertToString(String certSignReq,
     }
   }
 
+  /**
+   * A certificate for a role is only issued to the service user of that role.
+   */
+  private void checkRequesterIsService(NodeType role) throws IOException {
+    final String principalKey = getKerberosPrincipalKey(role);
+    final UserGroupInformation user = HddsServerUtil.getRemoteUser();
+    if (principalKey == null || !HddsServerUtil.isServiceUser(user, config.get(principalKey, ""))) {
+      throw new SCMSecurityException("User " + user.getUserName() + " is not allowed to request a " + role
+          + " certificate, it does not match the principal configured as " + principalKey + " on SCM.",
+          UNABLE_TO_ISSUE_CERTIFICATE);
+    }
+  }
+
+  private static String getKerberosPrincipalKey(NodeType role) {
+    switch (role) {
+    case DATANODE:
+      return HDDS_DATANODE_KERBEROS_PRINCIPAL_KEY;
+    case OM:
+      // TODO: move OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY to hdds-common.
+      return "ozone.om.kerberos.principal";
+    case SCM:
+      return HDDS_SCM_KERBEROS_PRINCIPAL_KEY;
+    case RECON:
+      return OZONE_RECON_KERBEROS_PRINCIPAL_KEY;
+    default:
+      return null;
+    }
+  }
+
   private SCMSecurityException generateException(Exception ex, NodeType role) {
     SCMSecurityException.ErrorCode errorCode;
     if (role == NodeType.SCM) {
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtil.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtil.java
index 2878304f386..0837274ea6e 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtil.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/TestHddsServerUtil.java
@@ -21,6 +21,9 @@
 import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_DEFAULT;
 import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_DATANODE_PORT_KEY;
 import static org.apache.hadoop.hdds.utils.HddsServerUtil.getSCMAddressForDatanodes;
+import static org.apache.hadoop.hdds.utils.HddsServerUtil.isServiceUser;
+import static org.apache.hadoop.security.UserGroupInformation.createProxyUser;
+import static org.apache.hadoop.security.UserGroupInformation.createRemoteUser;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -39,6 +42,7 @@
 import org.apache.hadoop.hdds.utils.HddsServerUtil;
 import org.apache.hadoop.net.NetUtils;
 import org.apache.hadoop.ozone.ha.ConfUtils;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -306,4 +310,16 @@ void testGetSCMAddressesWithHAConfig() {
 
   }
 
+  @Test
+  void testIsServiceUser() {
+    UserGroupInformation.setConfiguration(new OzoneConfiguration());
+    String principal = "om/_HOST@EXAMPLE.COM";
+    assertThat(isServiceUser(createRemoteUser("om/host1@EXAMPLE.COM"), principal)).isTrue();
+    for (String user : new String[] {"om@EXAMPLE.COM", "om/host1@OTHER.COM", "om/host1", "alice/om@EXAMPLE.COM"}) {
+      assertThat(isServiceUser(createRemoteUser(user), principal)).as(user).isFalse();
+    }
+    assertThat(isServiceUser(createProxyUser("om/host1@EXAMPLE.COM", createRemoteUser("alice")), principal)).isFalse();
+    // No user matches a principal that is not configured.
+    assertThat(isServiceUser(createRemoteUser("om/host1@EXAMPLE.COM"), "")).isFalse();
+  }
 }
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
index f02fe8e6469..a707c4ef143 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
@@ -57,6 +57,7 @@
 import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS;
 import static org.apache.ozone.test.GenericTestUtils.PortAllocator.getFreePort;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -98,6 +99,7 @@
 import org.apache.commons.validator.routines.DomainValidator;
 import org.apache.hadoop.hdds.HddsConfigKeys;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.MockDatanodeDetails;
 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;
@@ -395,6 +397,7 @@ void testSCMSecurityProtocol() throws Exception {
             () -> securityClient.getCertificate("1"));
         assertThat(ioException).hasMessageContaining(cannotAuthMessage);
       }
+      assertScmIssuesCertificateOnlyToServiceUserOfRole();
     } finally {
       if (scm != null) {
         scm.stop();
@@ -402,6 +405,57 @@ void testSCMSecurityProtocol() throws Exception {
     }
   }
 
+  /** SCM security protocol - a certificate is only issued to the service user of the requested role. */
+  private void assertScmIssuesCertificateOnlyToServiceUserOfRole() throws Exception {
+    String csr = newCsr(false);
+    HddsProtos.ScmNodeDetailsProto scmInfo = HddsProtos.ScmNodeDetailsProto.newBuilder()
+        .setClusterId(clusterId).setHostName("localhost").setScmNodeId(scmId).build();
+
+    UserGroupInformation testUser = UserGroupInformation.loginUserFromKeytabAndReturnUGI(
+        testUserPrincipal, testUserKeytab.getCanonicalPath());
+    try (SCMSecurityProtocolClientSideTranslatorPB securityClient = getScmSecurityClient(conf, testUser)) {
+      assertThatThrownBy(() -> securityClient.getOMCertificate(omInfo, csr))
+          .hasMessageContaining("is not allowed to request a OM certificate");
+      assertThatThrownBy(() -> securityClient.getDataNodeCertificate(
+          MockDatanodeDetails.randomDatanodeDetails().getProtoBufMessage(), csr))
+          .hasMessageContaining("is not allowed to request a DATANODE certificate");
+      // SCM role certificates are refused to non admins before the service user check.
+      assertThatThrownBy(() -> securityClient.getSCMCertificate(scmInfo, csr)).hasMessageContaining("Access denied");
+      assertThatThrownBy(() -> securityClient.getCertificate(HddsProtos.NodeDetailsProto.newBuilder()
+          .setUuid(omId).setClusterId(clusterId).setHostName("localhost").setNodeType(HddsProtos.NodeType.RECON)
+          .build(), csr))
+          .hasMessageContaining("is not allowed to request a RECON certificate");
+    }
+
+    UserGroupInformation omUser = UserGroupInformation.loginUserFromKeytabAndReturnUGI(
+        conf.get(OZONE_OM_KERBEROS_PRINCIPAL_KEY), conf.get(OZONE_OM_KERBEROS_KEYTAB_FILE_KEY));
+    try (SCMSecurityProtocolClientSideTranslatorPB securityClient = getScmSecurityClient(conf, omUser)) {
+      // OM gets a certificate for itself, but not one that can sign other certificates.
+      String caCsr = newCsr(true);
+      assertThatThrownBy(() -> securityClient.getOMCertificate(omInfo, caCsr))
+          .hasMessageContaining("Failed to verify the CSR");
+      assertThat(securityClient.getOMCertificate(omInfo, csr)).isNotEmpty();
+      // The service user of one role does not get a certificate of another role.
+      assertThatThrownBy(() -> securityClient.getDataNodeCertificate(
+          MockDatanodeDetails.randomDatanodeDetails().getProtoBufMessage(), csr))
+          .hasMessageContaining("is not allowed to request a DATANODE certificate");
+    }
+  }
+
+  private String newCsr(boolean ca) throws Exception {
+    CertificateSignRequest.Builder csrBuilder = new CertificateSignRequest.Builder()
+        .setKey(keyPair)
+        .setConfiguration(new SecurityConfig(conf))
+        .setScmID(scmId)
+        .setClusterID(clusterId)
+        .setSubject("test")
+        .setCA(ca)
+        .setDigitalSignature(true)
+        .setDigitalEncryption(true);
+    addIpAndDnsDataToBuilder(csrBuilder);
+    return csrBuilder.build().toEncodedFormat();
+  }
+
   @Test
   void testAdminAccessControlException() throws Exception {
     initSCM();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org