diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/exception/SCMSecurityException.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/exception/SCMSecurityException.java
index 7e008afc416b..13b8395391e0 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/exception/SCMSecurityException.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/exception/SCMSecurityException.java
@@ -109,6 +109,8 @@ public enum ErrorCode {
MISSING_BLOCK_TOKEN,
BLOCK_TOKEN_VERIFICATION_FAILED,
GET_ROOT_CA_CERT_FAILED,
- NOT_A_PRIMARY_SCM
+ NOT_A_PRIMARY_SCM,
+ SECRET_KEY_NOT_ENABLED,
+ SECRET_KEY_NOT_INITIALIZED
}
}
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/util/ProtobufUtils.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/util/ProtobufUtils.java
new file mode 100644
index 000000000000..428157981e3a
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/util/ProtobufUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+
+import java.util.UUID;
+
+/**
+ * Contains utilities to ease common protobuf to java object conversions.
+ */
+public final class ProtobufUtils {
+ private ProtobufUtils() {
+ }
+
+ public static HddsProtos.UUID toProtobuf(UUID uuid) {
+ return HddsProtos.UUID.newBuilder()
+ .setMostSigBits(uuid.getMostSignificantBits())
+ .setLeastSigBits(uuid.getLeastSignificantBits())
+ .build();
+ }
+
+ public static UUID fromProtobuf(HddsProtos.UUID proto) {
+ return new UUID(proto.getMostSigBits(), proto.getLeastSigBits());
+ }
+}
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/TestProtobufUtils.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/TestProtobufUtils.java
new file mode 100644
index 000000000000..fe6a57846c2c
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/TestProtobufUtils.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hdds.utils;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.apache.hadoop.util.ProtobufUtils.fromProtobuf;
+import static org.apache.hadoop.util.ProtobufUtils.toProtobuf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test-cases for {@link org.apache.hadoop.util.ProtobufUtils}.
+ */
+public class TestProtobufUtils {
+ @Test
+ public void testUuidToProtobuf() {
+ UUID object = UUID.randomUUID();
+ HddsProtos.UUID protobuf = toProtobuf(object);
+ assertEquals(object.getLeastSignificantBits(), protobuf.getLeastSigBits());
+ assertEquals(object.getMostSignificantBits(), protobuf.getMostSigBits());
+ }
+
+ @Test
+ public void testUuidConversion() {
+ UUID original = UUID.randomUUID();
+ HddsProtos.UUID protobuf = toProtobuf(original);
+ UUID deserialized = fromProtobuf(protobuf);
+ assertEquals(original, deserialized);
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
index 26107d54acc3..1cfe568d8ac9 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocol/SCMSecurityProtocol.java
@@ -18,6 +18,7 @@
import java.io.IOException;
import java.util.List;
+import java.util.UUID;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -26,6 +27,7 @@
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ScmNodeDetailsProto;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeDetailsProto;
import org.apache.hadoop.hdds.scm.ScmConfig;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.security.KerberosInfo;
@@ -170,4 +172,25 @@ long revokeCertificates(List certIds, int reason, long revocationTime)
*/
String getCertificate(NodeDetailsProto nodeDetails,
String certSignReq) throws IOException;
+
+
+ /**
+ * Get the current SecretKey that is used for signing tokens.
+ * @return ManagedSecretKey
+ */
+ ManagedSecretKey getCurrentSecretKey() throws IOException;
+
+ /**
+ * Get a particular SecretKey by ID.
+ *
+ * @param id the id to get SecretKey.
+ * @return ManagedSecretKey.
+ */
+ ManagedSecretKey getSecretKey(UUID id) throws IOException;
+
+ /**
+ * Get all the non-expired SecretKey managed by SCM.
+ * @return list of ManagedSecretKey.
+ */
+ List getAllSecretKeys() throws IOException;
}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
index bbbad8ba1d06..46ef8854c8a1 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/protocolPB/SCMSecurityProtocolClientSideTranslatorPB.java
@@ -22,7 +22,9 @@
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
@@ -40,6 +42,8 @@
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCrlsRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetDataNodeCertRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertRequestProto;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSecretKeyRequest;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSecretKeyResponse;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCACertificateRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetLatestCrlIdRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCertificateRequestProto;
@@ -51,6 +55,7 @@
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.Type;
import org.apache.hadoop.hdds.scm.proxy.SCMSecurityProtocolFailoverProxyProvider;
import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.tracing.TracingUtil;
import org.apache.hadoop.io.retry.RetryProxy;
@@ -60,6 +65,7 @@
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
+
import static org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetOMCertRequestProto;
/**
@@ -189,6 +195,38 @@ public String getCertificate(NodeDetailsProto nodeDetails,
.getX509Certificate();
}
+ @Override
+ public ManagedSecretKey getCurrentSecretKey() throws IOException {
+ SCMSecurityProtocolProtos.ManagedSecretKey secretKeyProto =
+ submitRequest(Type.GetCurrentSecretKey, builder -> {
+ }).getCurrentSecretKeyResponseProto().getSecretKey();
+ return ManagedSecretKey.fromProtobuf(secretKeyProto);
+ }
+
+ @Override
+ public ManagedSecretKey getSecretKey(UUID id) throws IOException {
+ SCMGetSecretKeyRequest request = SCMGetSecretKeyRequest.newBuilder()
+ .setSecretKeyId(HddsProtos.UUID.newBuilder()
+ .setMostSigBits(id.getMostSignificantBits())
+ .setLeastSigBits(id.getLeastSignificantBits())).build();
+ SCMGetSecretKeyResponse response = submitRequest(Type.GetSecretKey,
+ builder -> builder.setGetSecretKeyRequest(request))
+ .getGetSecretKeyResponseProto();
+
+ return response.hasSecretKey() ?
+ ManagedSecretKey.fromProtobuf(response.getSecretKey()) : null;
+ }
+
+ @Override
+ public List getAllSecretKeys() throws IOException {
+ List secretKeysList =
+ submitRequest(Type.GetAllSecretKeys, builder -> {
+ }).getSecretKeysListResponseProto().getSecretKeysList();
+ return secretKeysList.stream()
+ .map(ManagedSecretKey::fromProtobuf)
+ .collect(Collectors.toList());
+ }
+
/**
* Get signed certificate for SCM node.
*
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/ManagedSecretKey.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/ManagedSecretKey.java
index 7e8aaacb4871..3128265e9a46 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/ManagedSecretKey.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/ManagedSecretKey.java
@@ -19,8 +19,8 @@
package org.apache.hadoop.hdds.security.symmetric;
import com.google.protobuf.ByteString;
-import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos;
+import org.apache.hadoop.util.ProtobufUtils;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -92,13 +92,8 @@ public String toString() {
* @return the protobuf message to deserialize this object.
*/
public SCMSecurityProtocolProtos.ManagedSecretKey toProtobuf() {
- HddsProtos.UUID uuid = HddsProtos.UUID.newBuilder()
- .setMostSigBits(this.id.getMostSignificantBits())
- .setLeastSigBits(this.id.getLeastSignificantBits())
- .build();
-
return SCMSecurityProtocolProtos.ManagedSecretKey.newBuilder()
- .setId(uuid)
+ .setId(ProtobufUtils.toProtobuf(id))
.setCreationTime(this.creationTime.toEpochMilli())
.setExpiryTime(this.expiryTime.toEpochMilli())
.setAlgorithm(this.secretKey.getAlgorithm())
@@ -111,8 +106,7 @@ public SCMSecurityProtocolProtos.ManagedSecretKey toProtobuf() {
*/
public static ManagedSecretKey fromProtobuf(
SCMSecurityProtocolProtos.ManagedSecretKey message) {
- UUID id = new UUID(message.getId().getMostSigBits(),
- message.getId().getLeastSigBits());
+ UUID id = ProtobufUtils.fromProtobuf(message.getId());
Instant creationTime = Instant.ofEpochMilli(message.getCreationTime());
Instant expiryTime = Instant.ofEpochMilli(message.getExpiryTime());
SecretKey secretKey = new SecretKeySpec(message.getEncoded().toByteArray(),
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 0dc5bf89023f..cb529e10d1b4 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
@@ -129,6 +129,18 @@ public synchronized boolean checkAndRotate() throws TimeoutException {
return false;
}
+ public ManagedSecretKey getCurrentKey() {
+ return state.getCurrentKey();
+ }
+
+ public ManagedSecretKey getKey(UUID id) {
+ return state.getKey(id);
+ }
+
+ public List getSortedKeys() {
+ return state.getSortedKeys();
+ }
+
private boolean shouldRotate(ManagedSecretKey currentKey) {
Duration established = between(currentKey.getCreationTime(), Instant.now());
return established.compareTo(rotationDuration) >= 0;
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyState.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyState.java
index 7be70b4b029b..7b510a10b250 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyState.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyState.java
@@ -21,6 +21,7 @@
import org.apache.hadoop.hdds.scm.metadata.Replicate;
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
@@ -36,6 +37,8 @@ public interface SecretKeyState {
*/
ManagedSecretKey getCurrentKey();
+ ManagedSecretKey getKey(UUID id);
+
/**
* Get the keys that managed by this manager.
* The returned keys are sorted by creation time, in the order of latest
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyStateImpl.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyStateImpl.java
index d5c886fd99e6..b1d66e11863a 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyStateImpl.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/symmetric/SecretKeyStateImpl.java
@@ -23,12 +23,16 @@
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Function;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
/**
* Default implementation of {@link SecretKeyState}.
@@ -41,6 +45,7 @@ public final class SecretKeyStateImpl implements SecretKeyState {
private List sortedKeys;
private ManagedSecretKey currentKey;
+ private Map keyById;
private final SecretKeyStore keyStore;
@@ -66,6 +71,20 @@ public ManagedSecretKey getCurrentKey() {
}
}
+ @Override
+ public ManagedSecretKey getKey(UUID id) {
+ lock.readLock().lock();
+ try {
+ // Return null if not initialized yet.
+ if (keyById == null) {
+ return null;
+ }
+ return keyById.get(id);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
/**
* Get the keys that managed by this manager.
* The returned keys are sorted by creation time, in the order of latest
@@ -98,6 +117,10 @@ public void updateKeys(List newKeys) {
.collect(toList())
);
currentKey = sortedKeys.get(0);
+ keyById = newKeys.stream().collect(toMap(
+ ManagedSecretKey::getId,
+ Function.identity()
+ ));
LOG.info("Current key updated {}", currentKey);
keyStore.save(sortedKeys);
} finally {
diff --git a/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto b/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
index 3621018fa8e3..27d1e3c1c355 100644
--- a/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
+++ b/hadoop-hdds/interface-server/src/main/proto/ScmServerSecurityProtocol.proto
@@ -56,6 +56,7 @@ message SCMSecurityRequest {
optional SCMGetLatestCrlIdRequestProto getLatestCrlIdRequest = 11;
optional SCMRevokeCertificatesRequestProto revokeCertificatesRequest = 12;
optional SCMGetCertRequestProto getCertRequest = 13;
+ optional SCMGetSecretKeyRequest getSecretKeyRequest = 14;
}
message SCMSecurityResponse {
@@ -81,6 +82,12 @@ message SCMSecurityResponse {
optional SCMRevokeCertificatesResponseProto revokeCertificatesResponseProto = 10;
+ optional SCMGetCurrentSecretKeyResponse currentSecretKeyResponseProto = 11;
+
+ optional SCMGetSecretKeyResponse getSecretKeyResponseProto = 12;
+
+ optional SCMSecretKeysListResponse secretKeysListResponseProto = 13;
+
}
enum Type {
@@ -96,6 +103,9 @@ enum Type {
GetLatestCrlId = 10;
RevokeCertificates = 11;
GetCert = 12;
+ GetCurrentSecretKey = 13;
+ GetSecretKey = 14;
+ GetAllSecretKeys = 15;
}
enum Status {
@@ -116,6 +126,8 @@ enum Status {
GET_ROOT_CA_CERTIFICATE_FAILED = 15;
NOT_A_PRIMARY_SCM = 16;
REVOKE_CERTIFICATE_FAILED = 17;
+ SECRET_KEY_NOT_ENABLED = 18;
+ SECRET_KEY_NOT_INITIALIZED = 19;
}
/**
* This message is send by data node to prove its identity and get an SCM
@@ -258,3 +270,21 @@ message ManagedSecretKey {
required bytes encoded = 5;
}
+message SCMGetSecretKeyRequest {
+ required UUID secretKeyId = 1;
+}
+
+message SCMGetCurrentSecretKeyResponse {
+ required ManagedSecretKey secretKey = 1;
+}
+
+message SCMGetSecretKeyResponse {
+ optional ManagedSecretKey secretKey = 1;
+}
+
+message SCMSecretKeysListResponse {
+ repeated ManagedSecretKey secretKeys = 1;
+}
+
+
+
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
index 9d5d72aa6ecd..26f670a08965 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/SCMSecurityProtocolServerSideTranslatorPB.java
@@ -18,6 +18,7 @@
import java.io.IOException;
import java.util.List;
+import java.util.UUID;
import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos;
@@ -26,16 +27,20 @@
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertificateRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCrlsRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCrlsResponseProto;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCurrentSecretKeyResponse;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetDataNodeCertRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetLatestCrlIdRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetLatestCrlIdResponseProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetOMCertRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSCMCertRequestProto;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSecretKeyRequest;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetSecretKeyResponse;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCertificateRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMListCertificateResponseProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRevokeCertificatesRequestProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMRevokeCertificatesResponseProto;
+import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecretKeysListResponse;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityRequest;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMSecurityResponse;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.Status;
@@ -43,6 +48,7 @@
import org.apache.hadoop.hdds.scm.ha.RatisUtil;
import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.server.OzoneProtocolMessageDispatcher;
import org.apache.hadoop.hdds.utils.ProtocolMessageMetrics;
@@ -50,6 +56,7 @@
import com.google.protobuf.ProtocolMessageEnum;
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
+import org.apache.hadoop.util.ProtobufUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -155,6 +162,21 @@ public SCMSecurityResponse processRequest(SCMSecurityRequest request)
getCertificate(request.getGetCertRequest()))
.build();
+ case GetCurrentSecretKey:
+ return scmSecurityResponse
+ .setCurrentSecretKeyResponseProto(getCurrentSecretKey())
+ .build();
+
+ case GetSecretKey:
+ return scmSecurityResponse.setGetSecretKeyResponseProto(
+ getSecretKey(request.getGetSecretKeyRequest()))
+ .build();
+
+ case GetAllSecretKeys:
+ return scmSecurityResponse
+ .setSecretKeysListResponseProto(getAllSecretKeys())
+ .build();
+
default:
throw new IllegalArgumentException(
"Unknown request type: " + request.getCmdType());
@@ -177,6 +199,34 @@ public SCMSecurityResponse processRequest(SCMSecurityRequest request)
}
}
+ private SCMSecretKeysListResponse getAllSecretKeys() throws IOException {
+ SCMSecretKeysListResponse.Builder builder =
+ SCMSecretKeysListResponse.newBuilder();
+ impl.getAllSecretKeys()
+ .stream().map(ManagedSecretKey::toProtobuf)
+ .forEach(builder::addSecretKeys);
+ return builder.build();
+ }
+
+ private SCMGetSecretKeyResponse getSecretKey(
+ SCMGetSecretKeyRequest getSecretKeyRequest) throws IOException {
+ SCMGetSecretKeyResponse.Builder builder =
+ SCMGetSecretKeyResponse.newBuilder();
+ UUID id = ProtobufUtils.fromProtobuf(getSecretKeyRequest.getSecretKeyId());
+ ManagedSecretKey secretKey = impl.getSecretKey(id);
+ if (secretKey != null) {
+ builder.setSecretKey(secretKey.toProtobuf());
+ }
+ return builder.build();
+ }
+
+ private SCMGetCurrentSecretKeyResponse getCurrentSecretKey()
+ throws IOException {
+ return SCMGetCurrentSecretKeyResponse.newBuilder()
+ .setSecretKey(impl.getCurrentSecretKey().toProtobuf())
+ .build();
+ }
+
/**
* Convert exception to corresponsing status.
* @param ex
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 27ce30a8a18b..1761f9799223 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
@@ -147,6 +147,10 @@ public void start() {
TimeUnit.MILLISECONDS);
}
+ public SecretKeyManager getSecretKeyManager() {
+ return secretKeyManager;
+ }
+
@Override
public void stop() {
scheduler.shutdownNow();
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 a4171e805adb..3a395fbd13f7 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
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@@ -43,12 +44,16 @@
import org.apache.hadoop.hdds.protocol.proto.HddsProtos.ScmNodeDetailsProto;
import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos;
import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolPB;
+import org.apache.hadoop.hdds.scm.exceptions.SCMException.ResultCodes;
import org.apache.hadoop.hdds.scm.update.server.SCMUpdateServiceGrpcServer;
import org.apache.hadoop.hdds.scm.update.client.UpdateServiceConfig;
import org.apache.hadoop.hdds.scm.update.server.SCMCRLStore;
import org.apache.hadoop.hdds.scm.exceptions.SCMException;
import org.apache.hadoop.hdds.scm.protocol.SCMSecurityProtocolServerSideTranslatorPB;
import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeyManager;
import org.apache.hadoop.hdds.security.x509.crl.CRLInfo;
import org.apache.hadoop.hdds.utils.HddsServerUtil;
import org.apache.hadoop.hdds.scm.ScmConfig;
@@ -68,6 +73,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nullable;
+
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;
@@ -93,15 +100,21 @@ public class SCMSecurityProtocolServer implements SCMSecurityProtocol {
private final ProtocolMessageMetrics metrics;
private final StorageContainerManager storageContainerManager;
+ // SecretKey may not be enabled when neither block token nor container
+ // token is enabled.
+ private final SecretKeyManager secretKeyManager;
+
SCMSecurityProtocolServer(OzoneConfiguration conf,
CertificateServer rootCertificateServer,
CertificateServer scmCertificateServer,
- X509Certificate rootCACert, StorageContainerManager scm)
+ X509Certificate rootCACert, StorageContainerManager scm,
+ @Nullable SecretKeyManager secretKeyManager)
throws IOException {
this.storageContainerManager = scm;
this.rootCertificateServer = rootCertificateServer;
this.scmCertificateServer = scmCertificateServer;
this.rootCACertificate = rootCACert;
+ this.secretKeyManager = secretKeyManager;
final int handlerCount =
conf.getInt(ScmConfigKeys.OZONE_SCM_SECURITY_HANDLER_COUNT_KEY,
ScmConfigKeys.OZONE_SCM_SECURITY_HANDLER_COUNT_DEFAULT);
@@ -163,6 +176,37 @@ public String getCertificate(
return getEncodedCertToString(certSignReq, nodeDetails.getNodeType());
}
+ @Override
+ public ManagedSecretKey getCurrentSecretKey() throws SCMSecurityException {
+ validateSecretKeyStatus();
+ return secretKeyManager.getCurrentKey();
+ }
+
+ @Override
+ public ManagedSecretKey getSecretKey(UUID id) throws SCMSecurityException {
+ validateSecretKeyStatus();
+ return secretKeyManager.getKey(id);
+ }
+
+ @Override
+ public List getAllSecretKeys() throws SCMSecurityException {
+ validateSecretKeyStatus();
+ return secretKeyManager.getSortedKeys();
+ }
+
+ private void validateSecretKeyStatus() throws SCMSecurityException {
+ if (secretKeyManager == null) {
+ throw new SCMSecurityException("Secret keys are not enabled.",
+ ErrorCode.SECRET_KEY_NOT_ENABLED);
+ }
+
+ if (!secretKeyManager.isInitialized()) {
+ throw new SCMSecurityException(
+ "Secret key initialization is not finished yet.",
+ ErrorCode.SECRET_KEY_NOT_INITIALIZED);
+ }
+ }
+
/**
* Get SCM signed certificate for OM.
*
@@ -380,7 +424,7 @@ public long revokeCertificates(List certIds, int reason,
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new SCMException("Fail to revoke certs",
- SCMException.ResultCodes.FAILED_TO_REVOKE_CERTIFICATES);
+ ResultCodes.FAILED_TO_REVOKE_CERTIFICATES);
}
}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
index 710b36359f94..f6b248882339 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
@@ -71,6 +71,7 @@
import org.apache.hadoop.hdds.scm.server.upgrade.SCMUpgradeFinalizationContext;
import org.apache.hadoop.hdds.scm.server.upgrade.ScmHAUnfinalizedStateValidationAction;
import org.apache.hadoop.hdds.scm.pipeline.WritableContainerFactory;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeyManager;
import org.apache.hadoop.hdds.security.token.ContainerTokenGenerator;
import org.apache.hadoop.hdds.security.token.ContainerTokenSecretManager;
import org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateStore;
@@ -289,6 +290,7 @@ public final class StorageContainerManager extends ServiceRuntimeInfoImpl
// container replicas.
private ContainerReplicaPendingOps containerReplicaPendingOps;
private final AtomicBoolean isStopped = new AtomicBoolean(false);
+ private final SecretKeyManagerService secretKeyManagerService;
/**
* Creates a new StorageContainerManager. Configuration will be
@@ -366,6 +368,14 @@ private StorageContainerManager(OzoneConfiguration conf,
initializeSystemManagers(conf, configurator);
+ if (isSecretKeyEnable(securityConfig)) {
+ secretKeyManagerService = new SecretKeyManagerService(scmContext, conf,
+ scmHAManager.getRatisServer());
+ serviceManager.register(secretKeyManagerService);
+ } else {
+ secretKeyManagerService = null;
+ }
+
// Authenticate SCM if security is enabled, this initialization can only
// be done after the metadata store is initialized.
if (OzoneSecurityUtil.isSecurityEnabled(conf)) {
@@ -713,13 +723,6 @@ private void initializeSystemManagers(OzoneConfiguration conf,
serviceManager.register(expiredContainerReplicaOpScrubber);
- if (isSecretKeyEnable(securityConfig)) {
- SecretKeyManagerService secretKeyManagerService =
- new SecretKeyManagerService(scmContext, conf,
- scmHAManager.getRatisServer());
- serviceManager.register(secretKeyManagerService);
- }
-
if (configurator.getContainerManager() != null) {
containerManager = configurator.getContainerManager();
} else {
@@ -849,12 +852,16 @@ certificateStore, new DefaultProfile(),
certificateStore, scmStorageConfig, new DefaultProfile());
}
+ SecretKeyManager secretKeyManager = secretKeyManagerService != null ?
+ secretKeyManagerService.getSecretKeyManager() : null;
+
// We need to pass getCACertificate as rootCA certificate,
// as for SCM CA is root-CA.
securityProtocolServer = new SCMSecurityProtocolServer(conf,
rootCertificateServer, scmCertificateServer,
scmCertificateClient != null ?
- scmCertificateClient.getCACertificate() : null, this);
+ scmCertificateClient.getCACertificate() : null, this,
+ secretKeyManager);
if (securityConfig.isContainerTokenEnabled()) {
containerTokenMgr = createContainerTokenSecretManager(configuration);
diff --git a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMSecurityProtocolServer.java b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMSecurityProtocolServer.java
index 21df4bd4f300..19571696879f 100644
--- a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMSecurityProtocolServer.java
+++ b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMSecurityProtocolServer.java
@@ -41,7 +41,7 @@ public void setUp() throws Exception {
config.set(OZONE_SCM_SECURITY_SERVICE_ADDRESS_KEY,
OZONE_SCM_SECURITY_SERVICE_BIND_HOST_DEFAULT + ":0");
securityProtocolServer = new SCMSecurityProtocolServer(config, null,
- null, null, null);
+ null, null, null, null);
}
@AfterEach
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
index 60cb1a16c9ef..d9f4fe508abe 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
@@ -382,7 +382,7 @@ public static class Builder extends MiniOzoneClusterImpl.Builder {
// StorageContainerManager constructors respectively). So we need to manage
// them separately, see initOMHAConfig() and initSCMHAConfig().
private final ReservedPorts omPorts = new ReservedPorts(3);
- private final ReservedPorts scmPorts = new ReservedPorts(3);
+ private final ReservedPorts scmPorts = new ReservedPorts(4);
/**
* Creates a new Builder.
@@ -673,12 +673,17 @@ private void initSCMHAConfig() {
scmServiceId, scmNodeId);
String scmGrpcPortKey = ConfUtils.addKeySuffixes(
ScmConfigKeys.OZONE_SCM_GRPC_PORT_KEY, scmServiceId, scmNodeId);
+ String scmSecurityAddrKey = ConfUtils.addKeySuffixes(
+ ScmConfigKeys.OZONE_SCM_SECURITY_SERVICE_ADDRESS_KEY, scmServiceId,
+ scmNodeId);
PrimitiveIterator.OfInt nodePorts = scmPorts.assign(scmNodeId);
PrimitiveIterator.OfInt rpcPorts = scmRpcPorts.assign(scmNodeId);
conf.set(scmAddrKey, "127.0.0.1");
conf.set(scmHttpAddrKey, "127.0.0.1:" + nodePorts.nextInt());
conf.set(scmHttpsAddrKey, "127.0.0.1:" + nodePorts.nextInt());
+ conf.set(scmSecurityAddrKey, "127.0.0.1:" + nodePorts.nextInt());
+ conf.set("ozone.scm.update.service.port", "0");
int ratisPort = nodePorts.nextInt();
conf.setInt(scmRatisPortKey, ratisPort);
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecretKeysApi.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecretKeysApi.java
new file mode 100644
index 000000000000..9ec8750d7871
--- /dev/null
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecretKeysApi.java
@@ -0,0 +1,329 @@
+/*
+ * 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;
+
+import org.apache.hadoop.hdds.annotation.InterfaceAudience;
+import org.apache.hadoop.hdds.conf.DefaultConfigManager;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.SCMSecurityProtocol;
+import org.apache.hadoop.hdds.scm.ScmConfig;
+import org.apache.hadoop.hdds.scm.server.SCMHTTPServerConfig;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
+import org.apache.hadoop.hdds.utils.HddsServerUtil;
+import org.apache.hadoop.minikdc.MiniKdc;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.ozone.test.GenericTestUtils;
+import org.apache.ratis.util.ExitUtils;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+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;
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_BLOCK_TOKEN_ENABLED;
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECRET_KEY_EXPIRY_DURATION;
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECRET_KEY_ROTATE_CHECK_DURATION;
+import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECRET_KEY_ROTATE_DURATION;
+import static org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBEROS_KEYTAB_FILE_KEY;
+import static org.apache.hadoop.hdds.scm.ScmConfig.ConfigStrings.HDDS_SCM_KERBEROS_PRINCIPAL_KEY;
+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.exception.SCMSecurityException.ErrorCode.SECRET_KEY_NOT_ENABLED;
+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;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_HTTP_KERBEROS_PRINCIPAL_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_KEYTAB_FILE_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY;
+import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Integration test to verify symmetric SecretKeys APIs in a secure cluster.
+ */
+@InterfaceAudience.Private
+public final class TestSecretKeysApi {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(TestSecretKeysApi.class);
+
+ @Rule
+ public Timeout timeout = Timeout.seconds(1600);
+
+ private MiniKdc miniKdc;
+ private OzoneConfiguration conf;
+ private File workDir;
+ private File ozoneKeytab;
+ private File spnegoKeytab;
+ private File testUserKeytab;
+ private String testUserPrincipal;
+ private String host;
+ private String clusterId;
+ private String scmId;
+ private MiniOzoneHAClusterImpl cluster;
+
+ @Before
+ public void init() throws Exception {
+ conf = new OzoneConfiguration();
+ conf.set(OZONE_SCM_CLIENT_ADDRESS_KEY, "localhost");
+
+ ExitUtils.disableSystemExit();
+
+ workDir = GenericTestUtils.getTestDir(getClass().getSimpleName());
+ clusterId = UUID.randomUUID().toString();
+ scmId = UUID.randomUUID().toString();
+
+ startMiniKdc();
+ setSecureConfig();
+ createCredentialsInKDC();
+ }
+
+ @After
+ public void stop() {
+ miniKdc.stop();
+ if (cluster != null) {
+ cluster.stop();
+ }
+ DefaultConfigManager.clearDefaultConfigs();
+ }
+
+ private void createCredentialsInKDC() throws Exception {
+ ScmConfig scmConfig = conf.getObject(ScmConfig.class);
+ SCMHTTPServerConfig httpServerConfig =
+ conf.getObject(SCMHTTPServerConfig.class);
+ createPrincipal(ozoneKeytab, scmConfig.getKerberosPrincipal());
+ createPrincipal(spnegoKeytab, httpServerConfig.getKerberosPrincipal());
+ createPrincipal(testUserKeytab, testUserPrincipal);
+ }
+
+ private void createPrincipal(File keytab, String... principal)
+ throws Exception {
+ miniKdc.createPrincipal(keytab, principal);
+ }
+
+ private void startMiniKdc() throws Exception {
+ Properties securityProperties = MiniKdc.createConf();
+ miniKdc = new MiniKdc(securityProperties, workDir);
+ miniKdc.start();
+ }
+
+ private void setSecureConfig() throws IOException {
+ conf.setBoolean(OZONE_SECURITY_ENABLED_KEY, true);
+ host = InetAddress.getLocalHost().getCanonicalHostName()
+ .toLowerCase();
+
+ conf.set(HADOOP_SECURITY_AUTHENTICATION, KERBEROS.name());
+
+ String curUser = UserGroupInformation.getCurrentUser().getUserName();
+ conf.set(OZONE_ADMINISTRATORS, curUser);
+
+ String realm = miniKdc.getRealm();
+ String hostAndRealm = host + "@" + realm;
+ conf.set(HDDS_SCM_KERBEROS_PRINCIPAL_KEY, "scm/" + hostAndRealm);
+ conf.set(HDDS_SCM_HTTP_KERBEROS_PRINCIPAL_KEY, "HTTP_SCM/" + hostAndRealm);
+ conf.set(OZONE_OM_KERBEROS_PRINCIPAL_KEY, "scm/" + hostAndRealm);
+ conf.set(OZONE_OM_HTTP_KERBEROS_PRINCIPAL_KEY, "HTTP_OM/" + hostAndRealm);
+ conf.set(DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, "scm/" + hostAndRealm);
+
+ ozoneKeytab = new File(workDir, "scm.keytab");
+ spnegoKeytab = new File(workDir, "http.keytab");
+ testUserKeytab = new File(workDir, "testuser.keytab");
+ testUserPrincipal = "test@" + realm;
+
+ conf.set(HDDS_SCM_KERBEROS_KEYTAB_FILE_KEY,
+ ozoneKeytab.getAbsolutePath());
+ conf.set(HDDS_SCM_HTTP_KERBEROS_KEYTAB_FILE_KEY,
+ spnegoKeytab.getAbsolutePath());
+ conf.set(OZONE_OM_KERBEROS_KEYTAB_FILE_KEY,
+ ozoneKeytab.getAbsolutePath());
+ conf.set(OZONE_OM_HTTP_KERBEROS_KEYTAB_FILE,
+ spnegoKeytab.getAbsolutePath());
+ conf.set(DFS_DATANODE_KERBEROS_KEYTAB_FILE_KEY,
+ ozoneKeytab.getAbsolutePath());
+ }
+
+
+ /**
+ * Test secret key apis in happy case.
+ */
+ @Test
+ public void testSecretKeyApiSuccess() throws Exception {
+ enableBlockToken();
+ // set a low rotation period, of 1s, expiry is 3s, expect 3 active keys
+ // at any moment.
+ conf.set(HDDS_SECRET_KEY_ROTATE_CHECK_DURATION, "100ms");
+ conf.set(HDDS_SECRET_KEY_ROTATE_DURATION, "1s");
+ conf.set(HDDS_SECRET_KEY_EXPIRY_DURATION, "3000ms");
+
+ startCluster();
+ SCMSecurityProtocol securityProtocol = getScmSecurityProtocol();
+
+ // start the test when keys are full.
+ GenericTestUtils.waitFor(() -> {
+ try {
+ return securityProtocol.getAllSecretKeys().size() >= 3;
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }, 100, 4_000);
+
+ ManagedSecretKey initialKey = securityProtocol.getCurrentSecretKey();
+ assertNotNull(initialKey);
+ List initialKeys = securityProtocol.getAllSecretKeys();
+ assertEquals(initialKey, initialKeys.get(0));
+ ManagedSecretKey lastKey = initialKeys.get(initialKeys.size() - 1);
+
+ LOG.info("Initial active key: {}", initialKey);
+ LOG.info("Initial keys: {}", initialKeys);
+
+ // wait for the next rotation.
+ GenericTestUtils.waitFor(() -> {
+ try {
+ ManagedSecretKey newCurrentKey = securityProtocol.getCurrentSecretKey();
+ return !newCurrentKey.equals(initialKey);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }, 100, 1500);
+ ManagedSecretKey updatedKey = securityProtocol.getCurrentSecretKey();
+ List updatedKeys = securityProtocol.getAllSecretKeys();
+
+ LOG.info("Updated active key: {}", updatedKey);
+ LOG.info("Updated keys: {}", updatedKeys);
+
+ assertEquals(updatedKey, updatedKeys.get(0));
+ assertEquals(initialKey, updatedKeys.get(1));
+ // ensure the last key from the previous cycle no longer managed.
+ assertTrue(lastKey.isExpired());
+ assertFalse(updatedKeys.contains(lastKey));
+
+ // assert getSecretKey by ID.
+ ManagedSecretKey keyById = securityProtocol.getSecretKey(
+ updatedKey.getId());
+ assertNotNull(keyById);
+ ManagedSecretKey nonExisting = securityProtocol.getSecretKey(
+ UUID.randomUUID());
+ assertNull(nonExisting);
+ }
+
+ /**
+ * Verify API behavior when block token is not enable.
+ */
+ @Test
+ public void testSecretKeyApiNotEnabled() throws Exception {
+ startCluster();
+ SCMSecurityProtocol securityProtocol = getScmSecurityProtocol();
+
+ SCMSecurityException ex = assertThrows(SCMSecurityException.class,
+ securityProtocol::getCurrentSecretKey);
+ assertEquals(SECRET_KEY_NOT_ENABLED, ex.getErrorCode());
+
+ ex = assertThrows(SCMSecurityException.class,
+ () -> securityProtocol.getSecretKey(UUID.randomUUID()));
+ assertEquals(SECRET_KEY_NOT_ENABLED, ex.getErrorCode());
+
+ ex = assertThrows(SCMSecurityException.class,
+ securityProtocol::getAllSecretKeys);
+ assertEquals(SECRET_KEY_NOT_ENABLED, ex.getErrorCode());
+ }
+
+ /**
+ * Verify API behavior when SCM leader fails.
+ */
+ @Test
+ public void testSecretKeyAfterSCMFailover() throws Exception {
+ enableBlockToken();
+ // set a long duration period, so that no rotation happens during SCM
+ // leader change.
+ conf.set(HDDS_SECRET_KEY_ROTATE_CHECK_DURATION, "10m");
+ conf.set(HDDS_SECRET_KEY_ROTATE_DURATION, "1d");
+ conf.set(HDDS_SECRET_KEY_EXPIRY_DURATION, "7d");
+
+ startCluster();
+ SCMSecurityProtocol securityProtocol = getScmSecurityProtocol();
+ List keysInitial = securityProtocol.getAllSecretKeys();
+ LOG.info("Keys before fail over: {}.", keysInitial);
+
+ // turn the current SCM leader off.
+ StorageContainerManager activeSCM = cluster.getActiveSCM();
+ cluster.shutdownStorageContainerManager(activeSCM);
+ // wait for
+ cluster.waitForSCMToBeReady();
+
+ List keysAfter = securityProtocol.getAllSecretKeys();
+ LOG.info("Keys after fail over: {}.", keysAfter);
+
+ assertEquals(keysInitial.size(), keysAfter.size());
+ for (int i = 0; i < keysInitial.size(); i++) {
+ assertEquals(keysInitial.get(i), keysAfter.get(i));
+ }
+ }
+
+ private void startCluster()
+ throws IOException, TimeoutException, InterruptedException {
+ OzoneManager.setTestSecureOmFlag(true);
+ MiniOzoneCluster.Builder builder = MiniOzoneCluster.newHABuilder(conf)
+ .setClusterId(clusterId)
+ .setSCMServiceId("TestSecretKey")
+ .setScmId(scmId)
+ .setNumDatanodes(3)
+ .setNumOfStorageContainerManagers(3)
+ .setNumOfOzoneManagers(1);
+
+ cluster = (MiniOzoneHAClusterImpl) builder.build();
+ cluster.waitForClusterToBeReady();
+ }
+
+ @NotNull
+ private SCMSecurityProtocol getScmSecurityProtocol() throws IOException {
+ UserGroupInformation ugi =
+ UserGroupInformation.loginUserFromKeytabAndReturnUGI(
+ testUserPrincipal, testUserKeytab.getCanonicalPath());
+ ugi.setAuthenticationMethod(KERBEROS);
+ SCMSecurityProtocol scmSecurityProtocolClient =
+ HddsServerUtil.getScmSecurityClient(conf, ugi);
+ assertNotNull(scmSecurityProtocolClient);
+ return scmSecurityProtocolClient;
+ }
+
+ private void enableBlockToken() {
+ conf.setBoolean(HDDS_BLOCK_TOKEN_ENABLED, true);
+ }
+}