diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java
index ee20a214c9..b38ee7cfd5 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java
@@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,6 +78,7 @@ public class SecurityConfig {
private final Duration certDuration;
private final String x509SignatureAlgo;
private final Boolean grpcBlockTokenEnabled;
+ private final int getMaxKeyLength;
private final String certificateDir;
private final String certificateFileName;
@@ -88,6 +90,9 @@ public class SecurityConfig {
public SecurityConfig(Configuration configuration) {
Preconditions.checkNotNull(configuration, "Configuration cannot be null");
this.configuration = configuration;
+ this.getMaxKeyLength = configuration.getInt(
+ OzoneConfigKeys.OZONE_MAX_KEY_LEN,
+ OzoneConfigKeys.OZONE_MAX_KEY_LEN_DEFAULT);
this.size = this.configuration.getInt(HDDS_KEY_LEN, HDDS_DEFAULT_KEY_LEN);
this.keyAlgo = this.configuration.get(HDDS_KEY_ALGORITHM,
HDDS_DEFAULT_KEY_ALGORITHM);
@@ -289,4 +294,8 @@ private Provider initSecurityProvider(String providerName) {
throw new SecurityException("Unknown security provider:" + provider);
}
}
+
+ public int getMaxKeyLength() {
+ return this.getMaxKeyLength;
+ }
}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSecretManager.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSecretManager.java
new file mode 100644
index 0000000000..3b833cb140
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSecretManager.java
@@ -0,0 +1,191 @@
+/**
+ * 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.security;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.AccessModeProto;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.util.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Map;
+/**
+ * SecretManager for Ozone Master block tokens.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class OzoneBlockTokenSecretManager extends
+ OzoneSecretManager {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(OzoneBlockTokenSecretManager.class);;
+ // Will be set by grpc clients for individual datanodes.
+ static final Text SERVICE = new Text("HDDS_SERVICE");
+ private final String omCertSerialId;
+
+ /**
+ * Create a secret manager.
+ *
+ * @param conf
+ * @param blockTokenExpirytime token expiry time for expired tokens in
+ * milliseconds
+ */
+ public OzoneBlockTokenSecretManager(OzoneConfiguration conf,
+ long blockTokenExpirytime, String omCertSerialId) {
+ super(conf, blockTokenExpirytime, blockTokenExpirytime, SERVICE, LOG);
+ this.omCertSerialId = omCertSerialId;
+ }
+
+ @Override
+ public OzoneBlockTokenIdentifier createIdentifier() {
+ throw new SecurityException("Ozone block token can't be created "
+ + "without owner and access mode information.");
+ }
+
+ public OzoneBlockTokenIdentifier createIdentifier(String owner,
+ String blockId, EnumSet modes, long maxLength) {
+ return new OzoneBlockTokenIdentifier(owner, blockId, modes,
+ getTokenExpiryTime(), omCertSerialId, maxLength);
+ }
+
+ /**
+ * Generate an block token for specified user, blockId.
+ *
+ * @param user
+ * @param blockId
+ * @param modes
+ * @param maxLength
+ * @return token
+ */
+ public Token generateToken(String user,
+ String blockId, EnumSet modes, long maxLength) {
+ OzoneBlockTokenIdentifier tokenIdentifier = createIdentifier(user,
+ blockId, modes, maxLength);
+ if (LOG.isTraceEnabled()) {
+ long expiryTime = tokenIdentifier.getExpiryDate();
+ String tokenId = tokenIdentifier.toString();
+ LOG.trace("Issued delegation token -> expiryTime:{},tokenId:{}",
+ expiryTime, tokenId);
+ }
+ return new Token<>(tokenIdentifier.getBytes(),
+ createPassword(tokenIdentifier), tokenIdentifier.getKind(), SERVICE);
+ }
+
+ /**
+ * Generate an block token for current user.
+ *
+ * @param blockId
+ * @param modes
+ * @return token
+ */
+ public Token generateToken(String blockId,
+ EnumSet modes, long maxLength) throws IOException {
+ UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+ String userID = (ugi == null ? null : ugi.getShortUserName());
+ return generateToken(userID, blockId, modes, maxLength);
+ }
+
+ @Override
+ public byte[] retrievePassword(OzoneBlockTokenIdentifier identifier)
+ throws InvalidToken {
+ validateToken(identifier);
+ return createPassword(identifier);
+ }
+
+ @Override
+ public long renewToken(Token token,
+ String renewer) throws IOException {
+ throw new UnsupportedOperationException("Renew token operation is not " +
+ "supported for ozone block tokens.");
+ }
+
+ @Override
+ public OzoneBlockTokenIdentifier cancelToken(Token
+ token, String canceller) throws IOException {
+ throw new UnsupportedOperationException("Cancel token operation is not " +
+ "supported for ozone block tokens.");
+ }
+
+ /**
+ * Find the OzoneBlockTokenInfo for the given token id, and verify that if the
+ * token is not expired.
+ */
+ public boolean validateToken(OzoneBlockTokenIdentifier identifier)
+ throws InvalidToken {
+ long now = Time.now();
+ if (identifier.getExpiryDate() < now) {
+ throw new InvalidToken("token " + formatTokenId(identifier) + " is " +
+ "expired, current time: " + Time.formatTime(now) +
+ " expiry time: " + identifier.getExpiryDate());
+ }
+
+ if (!verifySignature(identifier, createPassword(identifier))) {
+ throw new InvalidToken("Tampared/Inavalid token.");
+ }
+ return true;
+ }
+
+ /**
+ * Should be called before this object is used.
+ */
+ @Override
+ public synchronized void start(KeyPair keyPair) throws IOException {
+ super.start(keyPair);
+ removeExpiredKeys();
+ }
+
+ /**
+ * Returns expiry time by adding configured expiry time with current time.
+ *
+ * @return Expiry time.
+ */
+ private long getTokenExpiryTime() {
+ return Time.now() + getTokenRenewInterval();
+ }
+
+ /**
+ * Should be called before this object is used.
+ */
+ @Override
+ public synchronized void stop() throws IOException {
+ super.stop();
+ }
+
+ private synchronized void removeExpiredKeys() {
+ // TODO: handle roll private key/certificate
+ long now = Time.now();
+ for (Iterator> it = allKeys.entrySet()
+ .iterator(); it.hasNext();) {
+ Map.Entry e = it.next();
+ OzoneSecretKey key = e.getValue();
+ if (key.getExpiryDate() < now) {
+ it.remove();
+ }
+ }
+ }
+}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSecretManager.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSecretManager.java
new file mode 100644
index 0000000000..1b9414b592
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSecretManager.java
@@ -0,0 +1,455 @@
+/**
+ * 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.security;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ozone.security.OzoneSecretStore.OzoneManagerSecretState;
+import org.apache.hadoop.ozone.security.OzoneTokenIdentifier.TokenInfo;
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.HadoopKerberosName;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.util.Daemon;
+import org.apache.hadoop.util.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * SecretManager for Ozone Master. Responsible for signing identifiers with
+ * private key,
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class OzoneDelegationTokenSecretManager
+ extends OzoneSecretManager {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(OzoneDelegationTokenSecretManager.class);
+ private final Map currentTokens;
+ private final OzoneSecretStore store;
+ private Thread tokenRemoverThread;
+ private final long tokenRemoverScanInterval;
+ /**
+ * If the delegation token update thread holds this lock, it will not get
+ * interrupted.
+ */
+ private Object noInterruptsLock = new Object();
+
+ /**
+ * Create a secret manager.
+ *
+ * @param conf configuration.
+ * @param tokenMaxLifetime the maximum lifetime of the delegation tokens in
+ * milliseconds
+ * @param tokenRenewInterval how often the tokens must be renewed in
+ * milliseconds
+ * @param dtRemoverScanInterval how often the tokens are scanned for expired
+ * tokens in milliseconds
+ */
+ public OzoneDelegationTokenSecretManager(OzoneConfiguration conf,
+ long tokenMaxLifetime, long tokenRenewInterval,
+ long dtRemoverScanInterval, Text service) throws IOException {
+ super(conf, tokenMaxLifetime, tokenRenewInterval, service, LOG);
+ currentTokens = new ConcurrentHashMap();
+ this.tokenRemoverScanInterval = dtRemoverScanInterval;
+ this.store = new OzoneSecretStore(conf);
+ loadTokenSecretState(store.loadState());
+ }
+
+ @Override
+ public T createIdentifier() {
+ return (T) T.newInstance();
+ }
+
+ /**
+ * Create new Identifier with given,owner,renwer and realUser.
+ *
+ * @return T
+ */
+ public T createIdentifier(Text owner, Text renewer, Text realUser) {
+ return (T) T.newInstance(owner, renewer, realUser);
+ }
+
+ /**
+ * Returns {@link Token} for given identifier.
+ *
+ * @param owner
+ * @param renewer
+ * @param realUser
+ * @return Token
+ * @throws IOException to allow future exceptions to be added without breaking
+ * compatibility
+ */
+ public Token createToken(Text owner, Text renewer, Text realUser)
+ throws IOException {
+ T identifier = createIdentifier(owner, renewer, realUser);
+ updateIdentifierDetails(identifier);
+
+ byte[] password = createPassword(identifier.getBytes(),
+ getCurrentKey().getPrivateKey());
+ addToTokenStore(identifier, password);
+ Token token = new Token<>(identifier.getBytes(), password,
+ identifier.getKind(), getService());
+ if (LOG.isTraceEnabled()) {
+ long expiryTime = identifier.getIssueDate() + getTokenRenewInterval();
+ String tokenId = identifier.toStringStable();
+ LOG.trace("Issued delegation token -> expiryTime:{},tokenId:{}",
+ expiryTime, tokenId);
+ }
+ return token;
+ }
+
+ /**
+ * Stores given identifier in token store.
+ *
+ * @param identifier
+ * @param password
+ * @throws IOException
+ */
+ private void addToTokenStore(T identifier, byte[] password)
+ throws IOException {
+ TokenInfo tokenInfo = new TokenInfo(identifier.getIssueDate()
+ + getTokenRenewInterval(), password, identifier.getTrackingId());
+ currentTokens.put(identifier, tokenInfo);
+ store.storeToken(identifier, tokenInfo.getRenewDate());
+ }
+
+ /**
+ * Updates issue date, master key id and sequence number for identifier.
+ *
+ * @param identifier the identifier to validate
+ */
+ private void updateIdentifierDetails(T identifier) {
+ int sequenceNum;
+ long now = Time.monotonicNow();
+ sequenceNum = incrementDelegationTokenSeqNum();
+ identifier.setIssueDate(now);
+ identifier.setMasterKeyId(getCurrentKey().getKeyId());
+ identifier.setSequenceNumber(sequenceNum);
+ identifier.setMaxDate(Time.monotonicNow() + getTokenMaxLifetime());
+ }
+
+ /**
+ * Renew a delegation token.
+ *
+ * @param token the token to renew
+ * @param renewer the full principal name of the user doing the renewal
+ * @return the new expiration time
+ * @throws InvalidToken if the token is invalid
+ * @throws AccessControlException if the user can't renew token
+ */
+ @Override
+ public synchronized long renewToken(Token token, String renewer)
+ throws IOException {
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+ T id = (T) T.readProtoBuf(in);
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Token renewal for identifier: {}, total currentTokens: {}",
+ formatTokenId(id), currentTokens.size());
+ }
+
+ long now = Time.monotonicNow();
+ if (id.getMaxDate() < now) {
+ throw new InvalidToken(renewer + " tried to renew an expired token "
+ + formatTokenId(id) + " max expiration date: "
+ + Time.formatTime(id.getMaxDate())
+ + " currentTime: " + Time.formatTime(now));
+ }
+ validateToken(id);
+ if ((id.getRenewer() == null) || (id.getRenewer().toString().isEmpty())) {
+ throw new AccessControlException(renewer +
+ " tried to renew a token " + formatTokenId(id)
+ + " without a renewer");
+ }
+ if (!id.getRenewer().toString().equals(renewer)) {
+ throw new AccessControlException(renewer
+ + " tries to renew a token " + formatTokenId(id)
+ + " with non-matching renewer " + id.getRenewer());
+ }
+ OzoneSecretKey key = allKeys.get(id.getMasterKeyId());
+ if (key == null) {
+ throw new InvalidToken("Unable to find master key for keyId="
+ + id.getMasterKeyId()
+ + " from cache. Failed to renew an unexpired token "
+ + formatTokenId(id) + " with sequenceNumber="
+ + id.getSequenceNumber());
+ }
+ byte[] password = createPassword(token.getIdentifier(),
+ key.getPrivateKey());
+
+ long renewTime = Math.min(id.getMaxDate(), now + getTokenRenewInterval());
+ try {
+ addToTokenStore(id, password);
+ } catch (IOException e) {
+ LOG.error("Unable to update token " + id.getSequenceNumber(), e);
+ }
+ return renewTime;
+ }
+
+ /**
+ * Cancel a token by removing it from store and cache.
+ *
+ * @return Identifier of the canceled token
+ * @throws InvalidToken for invalid token
+ * @throws AccessControlException if the user isn't allowed to cancel
+ */
+ public T cancelToken(Token token, String canceller) throws IOException {
+ T id = (T) T.readProtoBuf(token.getIdentifier());
+ LOG.debug("Token cancellation requested for identifier: {}",
+ formatTokenId(id));
+
+ if (id.getUser() == null) {
+ throw new InvalidToken("Token with no owner " + formatTokenId(id));
+ }
+ String owner = id.getUser().getUserName();
+ Text renewer = id.getRenewer();
+ HadoopKerberosName cancelerKrbName = new HadoopKerberosName(canceller);
+ String cancelerShortName = cancelerKrbName.getShortName();
+ if (!canceller.equals(owner)
+ && (renewer == null || renewer.toString().isEmpty()
+ || !cancelerShortName
+ .equals(renewer.toString()))) {
+ throw new AccessControlException(canceller
+ + " is not authorized to cancel the token " + formatTokenId(id));
+ }
+ try {
+ store.removeToken(id);
+ } catch (IOException e) {
+ LOG.error("Unable to remove token " + id.getSequenceNumber(), e);
+ }
+ TokenInfo info = currentTokens.remove(id);
+ if (info == null) {
+ throw new InvalidToken("Token not found " + formatTokenId(id));
+ }
+ return id;
+ }
+
+ @Override
+ public byte[] retrievePassword(T identifier) throws InvalidToken {
+ return validateToken(identifier).getPassword();
+ }
+
+ /**
+ * Checks if TokenInfo for the given identifier exists in database and if the
+ * token is expired.
+ */
+ public TokenInfo validateToken(T identifier) throws InvalidToken {
+ TokenInfo info = currentTokens.get(identifier);
+ if (info == null) {
+ throw new InvalidToken("token " + formatTokenId(identifier)
+ + " can't be found in cache");
+ }
+ long now = Time.monotonicNow();
+ if (info.getRenewDate() < now) {
+ throw new InvalidToken("token " + formatTokenId(identifier) + " is " +
+ "expired, current time: " + Time.formatTime(now) +
+ " expected renewal time: " + Time.formatTime(info.getRenewDate()));
+ }
+ if (!verifySignature(identifier, info.getPassword())) {
+ throw new InvalidToken("Tampared/Inavalid token.");
+ }
+ return info;
+ }
+
+ // TODO: handle roll private key/certificate
+ private synchronized void removeExpiredKeys() {
+ long now = Time.monotonicNow();
+ for (Iterator> it = allKeys.entrySet()
+ .iterator(); it.hasNext();) {
+ Map.Entry e = it.next();
+ OzoneSecretKey key = e.getValue();
+ if (key.getExpiryDate() < now && key.getExpiryDate() != -1) {
+ if (!key.equals(getCurrentKey())) {
+ it.remove();
+ try {
+ store.removeTokenMasterKey(key);
+ } catch (IOException ex) {
+ LOG.error("Unable to remove master key " + key.getKeyId(), ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void loadTokenSecretState(OzoneManagerSecretState state)
+ throws IOException {
+ LOG.info("Loading token state into token manager.");
+ for (OzoneSecretKey key : state.ozoneManagerSecretState()) {
+ allKeys.putIfAbsent(key.getKeyId(), key);
+ incrementCurrentKeyId();
+ }
+ for (Map.Entry entry : state.getTokenState().entrySet()) {
+ addPersistedDelegationToken(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void addPersistedDelegationToken(
+ T identifier, long renewDate)
+ throws IOException {
+ if (isRunning()) {
+ // a safety check
+ throw new IOException(
+ "Can't add persisted delegation token to a running SecretManager.");
+ }
+ int keyId = identifier.getMasterKeyId();
+ OzoneSecretKey dKey = allKeys.get(keyId);
+ if (dKey == null) {
+ LOG.warn("No KEY found for persisted identifier "
+ + formatTokenId(identifier));
+ return;
+ }
+
+ PrivateKey privateKey = dKey.getPrivateKey();
+ byte[] password = createPassword(identifier.getBytes(), privateKey);
+ if (identifier.getSequenceNumber() > getDelegationTokenSeqNum()) {
+ setDelegationTokenSeqNum(identifier.getSequenceNumber());
+ }
+ if (currentTokens.get(identifier) == null) {
+ currentTokens.put(identifier, new TokenInfo(renewDate,
+ password, identifier.getTrackingId()));
+ } else {
+ throw new IOException("Same delegation token being added twice: "
+ + formatTokenId(identifier));
+ }
+ }
+
+ /**
+ * Should be called before this object is used.
+ */
+ @Override
+ public synchronized void start(KeyPair keyPair) throws IOException {
+ super.start(keyPair);
+ storeKey(getCurrentKey());
+ removeExpiredKeys();
+ tokenRemoverThread = new Daemon(new ExpiredTokenRemover());
+ tokenRemoverThread.start();
+ }
+
+ private void storeKey(OzoneSecretKey key) throws IOException {
+ store.storeTokenMasterKey(key);
+ if (!allKeys.containsKey(key.getKeyId())) {
+ allKeys.put(key.getKeyId(), key);
+ }
+ }
+
+ public void stopThreads() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Stopping expired delegation token remover thread");
+ }
+ setIsRunning(false);
+
+ if (tokenRemoverThread != null) {
+ synchronized (noInterruptsLock) {
+ tokenRemoverThread.interrupt();
+ }
+ try {
+ tokenRemoverThread.join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ "Unable to join on token removal thread", e);
+ }
+ }
+ }
+
+ /**
+ * Stops the OzoneDelegationTokenSecretManager.
+ *
+ * @throws IOException
+ */
+ @Override
+ public void stop() throws IOException {
+ super.stop();
+ stopThreads();
+ if (this.store != null) {
+ this.store.close();
+ }
+ }
+
+ /**
+ * Remove expired delegation tokens from cache and persisted store.
+ */
+ private void removeExpiredToken() {
+ long now = Time.monotonicNow();
+ synchronized (this) {
+ Iterator> i = currentTokens.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry entry = i.next();
+ long renewDate = entry.getValue().getRenewDate();
+ if (renewDate < now) {
+ i.remove();
+ try {
+ store.removeToken(entry.getKey());
+ } catch (IOException e) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Failed to remove expired token {}", entry.getValue());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class ExpiredTokenRemover extends Thread {
+ private long lastTokenCacheCleanup;
+
+ @Override
+ public void run() {
+ LOG.info("Starting expired delegation token remover thread, "
+ + "tokenRemoverScanInterval=" + getTokenRemoverScanInterval()
+ / (60 * 1000) + " min(s)");
+ try {
+ while (isRunning()) {
+ long now = Time.monotonicNow();
+ if (lastTokenCacheCleanup + getTokenRemoverScanInterval()
+ < now) {
+ removeExpiredToken();
+ lastTokenCacheCleanup = now;
+ }
+ try {
+ Thread.sleep(Math.min(5000,
+ getTokenRemoverScanInterval())); // 5 seconds
+ } catch (InterruptedException ie) {
+ LOG.error("ExpiredTokenRemover received " + ie);
+ }
+ }
+ } catch (Throwable t) {
+ LOG.error("ExpiredTokenRemover thread received unexpected exception",
+ t);
+ Runtime.getRuntime().exit(-1);
+ }
+ }
+ }
+
+ public long getTokenRemoverScanInterval() {
+ return tokenRemoverScanInterval;
+ }
+}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java
index 0c84404d65..01ef8bb6c8 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java
@@ -18,8 +18,17 @@
package org.apache.hadoop.ozone.security;
import com.google.common.base.Preconditions;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.token.SecretManager;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.slf4j.Logger;
+
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
@@ -27,25 +36,9 @@
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
-import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.io.Text;
-import org.apache.hadoop.ozone.OzoneConfigKeys;
-import org.apache.hadoop.ozone.security.OzoneSecretStore.OzoneManagerSecretState;
-import org.apache.hadoop.ozone.security.OzoneTokenIdentifier.TokenInfo;
-import org.apache.hadoop.security.AccessControlException;
-import org.apache.hadoop.security.HadoopKerberosName;
-import org.apache.hadoop.security.token.SecretManager;
-import org.apache.hadoop.security.token.Token;
-import org.apache.hadoop.util.Daemon;
-import org.apache.hadoop.util.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* SecretManager for Ozone Master. Responsible for signing identifiers with
@@ -53,33 +46,23 @@
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
-public class OzoneSecretManager
+public abstract class OzoneSecretManager
extends SecretManager {
- private static final Logger LOG = LoggerFactory
- .getLogger(OzoneSecretManager.class);
+ private final Logger logger;
/**
* The name of the Private/Public Key based hashing algorithm.
*/
- private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";
+ private final SecurityConfig securityConfig;
private final long tokenMaxLifetime;
private final long tokenRenewInterval;
- private final long tokenRemoverScanInterval;
private final Text service;
- private final Map allKeys;
- private final Map currentTokens;
- private final OzoneSecretStore store;
- private Thread tokenRemoverThread;
private volatile boolean running;
- private AtomicInteger tokenSequenceNumber;
private OzoneSecretKey currentKey;
- private AtomicInteger currentKeyId;
- /**
- * If the delegation token update thread holds this lock, it will not get
- * interrupted.
- */
- private Object noInterruptsLock = new Object();
private int maxKeyLength;
+ private AtomicInteger currentKeyId;
+ private AtomicInteger tokenSequenceNumber;
+ protected final Map allKeys;
/**
* Create a secret manager.
@@ -89,100 +72,21 @@ public class OzoneSecretManager
* milliseconds
* @param tokenRenewInterval how often the tokens must be renewed in
* milliseconds
- * @param dtRemoverScanInterval how often the tokens are scanned for expired
- * tokens in milliseconds
+ * @param service name of service
*/
public OzoneSecretManager(OzoneConfiguration conf, long tokenMaxLifetime,
- long tokenRenewInterval, long dtRemoverScanInterval, Text service)
- throws IOException {
+ long tokenRenewInterval, Text service, Logger logger) {
+ this.securityConfig = new SecurityConfig(conf);
this.tokenMaxLifetime = tokenMaxLifetime;
this.tokenRenewInterval = tokenRenewInterval;
- this.tokenRemoverScanInterval = dtRemoverScanInterval;
-
- currentTokens = new ConcurrentHashMap();
- allKeys = new ConcurrentHashMap<>();
currentKeyId = new AtomicInteger();
tokenSequenceNumber = new AtomicInteger();
- this.store = new OzoneSecretStore(conf);
- loadTokenSecretState(store.loadState());
+ allKeys = new ConcurrentHashMap<>();
this.service = service;
- this.maxKeyLength = conf.getInt(OzoneConfigKeys.OZONE_MAX_KEY_LEN,
- OzoneConfigKeys.OZONE_MAX_KEY_LEN_DEFAULT);
+ this.maxKeyLength = securityConfig.getMaxKeyLength();
+ this.logger = logger;
}
- @Override
- public T createIdentifier() {
- return (T) T.newInstance();
- }
-
- /**
- * Create new Identifier with given,owner,renwer and realUser.
- *
- * @return T
- */
- public T createIdentifier(Text owner, Text renewer, Text realUser) {
- return (T) T.newInstance(owner, renewer, realUser);
- }
-
- /**
- * Returns {@link Token} for given identifier.
- *
- * @param owner
- * @param renewer
- * @param realUser
- * @return Token
- * @throws IOException to allow future exceptions to be added without breaking
- * compatibility
- */
- public Token createToken(Text owner, Text renewer, Text realUser)
- throws IOException {
- T identifier = createIdentifier(owner, renewer, realUser);
- updateIdentifierDetails(identifier);
-
- byte[] password = createPassword(identifier.getBytes(),
- currentKey.getPrivateKey());
- addToTokenStore(identifier, password);
- Token token = new Token<>(identifier.getBytes(), password,
- identifier.getKind(), service);
- if (LOG.isTraceEnabled()) {
- long expiryTime = identifier.getIssueDate() + tokenRenewInterval;
- String tokenId = identifier.toStringStable();
- LOG.trace("Issued delegation token -> expiryTime:{},tokenId:{}",
- expiryTime, tokenId);
- }
-
- return token;
- }
-
- /**
- * Stores given identifier in token store.
- *
- * @param identifier
- * @param password
- * @throws IOException
- */
- private void addToTokenStore(T identifier, byte[] password)
- throws IOException {
- TokenInfo tokenInfo = new TokenInfo(identifier.getIssueDate()
- + tokenRenewInterval, password, identifier.getTrackingId());
- currentTokens.put(identifier, tokenInfo);
- store.storeToken(identifier, tokenInfo.getRenewDate());
- }
-
- /**
- * Updates issue date, master key id and sequence number for identifier.
- *
- * @param identifier the identifier to validate
- */
- private void updateIdentifierDetails(T identifier) {
- int sequenceNum;
- long now = Time.monotonicNow();
- sequenceNum = incrementDelegationTokenSeqNum();
- identifier.setIssueDate(now);
- identifier.setMasterKeyId(currentKey.getKeyId());
- identifier.setSequenceNumber(sequenceNum);
- identifier.setMaxDate(Time.monotonicNow() + tokenMaxLifetime);
- }
/**
* Compute HMAC of the identifier using the private key and return the output
@@ -196,7 +100,7 @@ public byte[] createPassword(byte[] identifier, PrivateKey privateKey)
throws OzoneSecurityException {
try {
Signature rsaSignature = Signature.getInstance(
- DEFAULT_SIGNATURE_ALGORITHM);
+ getDefaultSignatureAlgorithm());
rsaSignature.initSign(privateKey);
rsaSignature.update(identifier);
return rsaSignature.sign();
@@ -210,22 +114,31 @@ public byte[] createPassword(byte[] identifier, PrivateKey privateKey)
@Override
public byte[] createPassword(T identifier) {
- LOG.debug("Creating password for identifier: {}, currentKey: {}",
+ logger.debug("Creating password for identifier: {}, currentKey: {}",
formatTokenId(identifier), currentKey.getKeyId());
byte[] password = null;
try {
password = createPassword(identifier.getBytes(),
currentKey.getPrivateKey());
} catch (IOException ioe) {
- LOG.error("Could not store token {}!!", formatTokenId(identifier),
+ logger.error("Could not store token {}!!", formatTokenId(identifier),
ioe);
}
return password;
}
+ /**
+ * Default implementation for Ozone. Verifies if hash in token is legit.
+ * */
@Override
public byte[] retrievePassword(T identifier) throws InvalidToken {
- return checkToken(identifier).getPassword();
+ byte[] password = createPassword(identifier);
+ // TODO: Revisit this when key/certificate rotation is implemented.
+ // i.e Try all valid keys instead of current key only.
+ if (!verifySignature(identifier, password)) {
+ throw new InvalidToken("Tampared/Inavalid token.");
+ }
+ return password;
}
/**
@@ -237,52 +150,8 @@ public byte[] retrievePassword(T identifier) throws InvalidToken {
* @throws InvalidToken if the token is invalid
* @throws AccessControlException if the user can't renew token
*/
- public synchronized long renewToken(Token token, String renewer)
- throws IOException {
- ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
- DataInputStream in = new DataInputStream(buf);
- T id = (T) T.readProtoBuf(in);
- LOG.debug("Token renewal for identifier: {}, total currentTokens: {}",
- formatTokenId(id), currentTokens.size());
-
- long now = Time.monotonicNow();
- if (id.getMaxDate() < now) {
- throw new InvalidToken(renewer + " tried to renew an expired token "
- + formatTokenId(id) + " max expiration date: "
- + Time.formatTime(id.getMaxDate())
- + " currentTime: " + Time.formatTime(now));
- }
- checkToken(id);
- if ((id.getRenewer() == null) || (id.getRenewer().toString().isEmpty())) {
- throw new AccessControlException(renewer +
- " tried to renew a token " + formatTokenId(id)
- + " without a renewer");
- }
- if (!id.getRenewer().toString().equals(renewer)) {
- throw new AccessControlException(renewer
- + " tries to renew a token " + formatTokenId(id)
- + " with non-matching renewer " + id.getRenewer());
- }
- OzoneSecretKey key = allKeys.get(id.getMasterKeyId());
- if (key == null) {
- throw new InvalidToken("Unable to find master key for keyId="
- + id.getMasterKeyId()
- + " from cache. Failed to renew an unexpired token "
- + formatTokenId(id) + " with sequenceNumber="
- + id.getSequenceNumber());
- }
- byte[] password = createPassword(token.getIdentifier(),
- key.getPrivateKey());
-
- long renewTime = Math.min(id.getMaxDate(), now + tokenRenewInterval);
- try {
- addToTokenStore(id, password);
- } catch (IOException e) {
- LOG.error("Unable to update token " + id.getSequenceNumber(), e);
- }
- return renewTime;
- }
-
+ public abstract long renewToken(Token token, String renewer)
+ throws IOException;
/**
* Cancel a token by removing it from store and cache.
*
@@ -290,44 +159,8 @@ public synchronized long renewToken(Token token, String renewer)
* @throws InvalidToken for invalid token
* @throws AccessControlException if the user isn't allowed to cancel
*/
- public T cancelToken(Token token, String canceller) throws IOException {
- T id = (T) T.readProtoBuf(token.getIdentifier());
- LOG.debug("Token cancellation requested for identifier: {}",
- formatTokenId(id));
-
- if (id.getUser() == null) {
- throw new InvalidToken("Token with no owner " + formatTokenId(id));
- }
- String owner = id.getUser().getUserName();
- Text renewer = id.getRenewer();
- HadoopKerberosName cancelerKrbName = new HadoopKerberosName(canceller);
- String cancelerShortName = cancelerKrbName.getShortName();
- if (!canceller.equals(owner)
- && (renewer == null || renewer.toString().isEmpty()
- || !cancelerShortName
- .equals(renewer.toString()))) {
- throw new AccessControlException(canceller
- + " is not authorized to cancel the token " + formatTokenId(id));
- }
- try {
- store.removeToken(id);
- } catch (IOException e) {
- LOG.error("Unable to remove token " + id.getSequenceNumber(), e);
- }
- TokenInfo info = currentTokens.remove(id);
- if (info == null) {
- throw new InvalidToken("Token not found " + formatTokenId(id));
- }
- return id;
- }
-
- public int getCurrentKeyId() {
- return currentKeyId.get();
- }
-
- public void setCurrentKeyId(int keyId) {
- currentKeyId.set(keyId);
- }
+ public abstract T cancelToken(Token token, String canceller)
+ throws IOException;
public int incrementCurrentKeyId() {
return currentKeyId.incrementAndGet();
@@ -346,14 +179,31 @@ public int incrementDelegationTokenSeqNum() {
}
/**
- * Validates if given token is valid.
+ * Update the current master key. This is called once by start method before
+ * tokenRemoverThread is created,
+ */
+ private OzoneSecretKey updateCurrentKey(KeyPair keyPair) throws IOException {
+ logger.info("Updating the current master key for generating tokens");
+
+ // TODO: fix me based on the certificate expire time to set the key
+ // expire time.
+ int newCurrentId = incrementCurrentKeyId();
+ OzoneSecretKey newKey = new OzoneSecretKey(newCurrentId, -1,
+ keyPair, maxKeyLength);
+ currentKey = newKey;
+ return currentKey;
+ }
+
+ /**
+ * Validates if given hash is valid.
*
* @param identifier
* @param password
*/
- private boolean validateToken(T identifier, byte[] password) {
+ public boolean verifySignature(T identifier, byte[] password) {
try {
- Signature rsaSignature = Signature.getInstance("SHA256withRSA");
+ Signature rsaSignature =
+ Signature.getInstance(getDefaultSignatureAlgorithm());
rsaSignature.initVerify(currentKey.getPublicKey());
rsaSignature.update(identifier.getBytes());
return rsaSignature.verify(password);
@@ -363,179 +213,45 @@ private boolean validateToken(T identifier, byte[] password) {
}
}
- /**
- * Checks if TokenInfo for the given identifier exists in database and if the
- * token is expired.
- */
- public TokenInfo checkToken(T identifier) throws InvalidToken {
- TokenInfo info = currentTokens.get(identifier);
- if (info == null) {
- throw new InvalidToken("token " + formatTokenId(identifier)
- + " can't be found in cache");
- }
- long now = Time.monotonicNow();
- if (info.getRenewDate() < now) {
- throw new InvalidToken("token " + formatTokenId(identifier) + " is " +
- "expired, current time: " + Time.formatTime(now) +
- " expected renewal time: " + Time.formatTime(info.getRenewDate()));
- }
- if (!validateToken(identifier, info.getPassword())) {
- throw new InvalidToken("Tampared/Inavalid token.");
- }
- return info;
- }
-
- // TODO: handle roll private key/certificate
- private synchronized void removeExpiredKeys() {
- long now = Time.monotonicNow();
- for (Iterator> it = allKeys.entrySet()
- .iterator(); it.hasNext();) {
- Map.Entry e = it.next();
- OzoneSecretKey key = e.getValue();
- if (key.getExpiryDate() < now && key.getExpiryDate() != -1) {
- if (!key.equals(currentKey)) {
- it.remove();
- try {
- store.removeTokenMasterKey(key);
- } catch (IOException ex) {
- LOG.error("Unable to remove master key " + key.getKeyId(), ex);
- }
- }
- }
- }
- }
-
- private void loadTokenSecretState(OzoneManagerSecretState state)
- throws IOException {
- LOG.info("Loading token state into token manager.");
- for (OzoneSecretKey key : state.ozoneManagerSecretState()) {
- allKeys.putIfAbsent(key.getKeyId(), key);
- }
- for (Map.Entry entry : state.getTokenState().entrySet()) {
- addPersistedDelegationToken(entry.getKey(), entry.getValue());
- }
- }
-
- private String formatTokenId(T id) {
+ public String formatTokenId(T id) {
return "(" + id + ")";
}
- private void addPersistedDelegationToken(
- T identifier, long renewDate)
- throws IOException {
- if (running) {
- // a safety check
- throw new IOException(
- "Can't add persisted delegation token to a running SecretManager.");
- }
- int keyId = identifier.getMasterKeyId();
- OzoneSecretKey dKey = allKeys.get(keyId);
- if (dKey == null) {
- LOG.warn("No KEY found for persisted identifier "
- + formatTokenId(identifier));
- return;
- }
-
- PrivateKey privateKey = dKey.getPrivateKey();
- byte[] password = createPassword(identifier.getBytes(), privateKey);
- if (identifier.getSequenceNumber() > getDelegationTokenSeqNum()) {
- setDelegationTokenSeqNum(identifier.getSequenceNumber());
- }
- if (currentTokens.get(identifier) == null) {
- currentTokens.put(identifier, new TokenInfo(renewDate,
- password, identifier.getTrackingId()));
- } else {
- throw new IOException("Same delegation token being added twice: "
- + formatTokenId(identifier));
- }
- }
-
/**
* Should be called before this object is used.
+ *
+ * @param keyPair
+ * @throws IOException
*/
- public void startThreads(KeyPair keyPair) throws IOException {
- Preconditions.checkState(!running);
+ public synchronized void start(KeyPair keyPair) throws IOException {
+ Preconditions.checkState(!isRunning());
updateCurrentKey(keyPair);
- removeExpiredKeys();
- synchronized (this) {
- running = true;
- tokenRemoverThread = new Daemon(new ExpiredTokenRemover());
- tokenRemoverThread.start();
- }
- }
-
- public void stopThreads() {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Stopping expired delegation token remover thread");
- }
- running = false;
-
- if (tokenRemoverThread != null) {
- synchronized (noInterruptsLock) {
- tokenRemoverThread.interrupt();
- }
- try {
- tokenRemoverThread.join();
- } catch (InterruptedException e) {
- throw new RuntimeException(
- "Unable to join on token removal thread", e);
- }
- }
+ setIsRunning(true);
}
/**
- * Stops the OzoneSecretManager.
+ * Stops the OzoneDelegationTokenSecretManager.
*
* @throws IOException
*/
- public void stop() throws IOException {
- stopThreads();
- if (this.store != null) {
- this.store.close();
- }
+ public synchronized void stop() throws IOException {
+ setIsRunning(false);
}
- /**
- * Update the current master key. This is called once by startThreads before
- * tokenRemoverThread is created,
- */
- private void updateCurrentKey(KeyPair keyPair) throws IOException {
- LOG.info("Updating the current master key for generating tokens");
-
- // TODO: fix me based on the certificate expire time to set the key
- // expire time.
- int newCurrentId = incrementCurrentKeyId();
- OzoneSecretKey newKey = new OzoneSecretKey(newCurrentId, -1,
- keyPair, maxKeyLength);
-
- store.storeTokenMasterKey(newKey);
- if (!allKeys.containsKey(newKey.getKeyId())) {
- allKeys.put(newKey.getKeyId(), newKey);
- }
-
- synchronized (this) {
- currentKey = newKey;
- }
+ public String getDefaultSignatureAlgorithm() {
+ return securityConfig.getSignatureAlgo();
}
- /**
- * Remove expired delegation tokens from cache and persisted store.
- */
- private void removeExpiredToken() throws IOException {
- long now = Time.monotonicNow();
- synchronized (this) {
- Iterator> i = currentTokens.entrySet().iterator();
- while (i.hasNext()) {
- Map.Entry entry = i.next();
- long renewDate = entry.getValue().getRenewDate();
- if (renewDate < now) {
- i.remove();
- store.removeToken(entry.getKey());
- }
- }
- }
+ public long getTokenMaxLifetime() {
+ return tokenMaxLifetime;
+ }
+
+ public long getTokenRenewInterval() {
+ return tokenRenewInterval;
+ }
+
+ public Text getService() {
+ return service;
}
/**
@@ -547,52 +263,20 @@ public synchronized boolean isRunning() {
return running;
}
- /**
- * Returns expiry time of a token given its identifier.
- *
- * @param dtId DelegationTokenIdentifier of a token
- * @return Expiry time of the token
- * @throws IOException
- */
- public long getTokenExpiryTime(T dtId)
- throws IOException {
- TokenInfo info = currentTokens.get(dtId);
- if (info != null) {
- return info.getRenewDate();
- } else {
- throw new IOException("No delegation token found for this identifier");
- }
+ public void setIsRunning(boolean val) {
+ running = val;
}
- private class ExpiredTokenRemover extends Thread {
- private long lastTokenCacheCleanup;
+ public OzoneSecretKey getCurrentKey() {
+ return currentKey;
+ }
- @Override
- public void run() {
- LOG.info("Starting expired delegation token remover thread, "
- + "tokenRemoverScanInterval=" + tokenRemoverScanInterval
- / (60 * 1000) + " min(s)");
- try {
- while (running) {
- long now = Time.monotonicNow();
- if (lastTokenCacheCleanup + tokenRemoverScanInterval
- < now) {
- removeExpiredToken();
- lastTokenCacheCleanup = now;
- }
- try {
- Thread.sleep(Math.min(5000,
- tokenRemoverScanInterval)); // 5 seconds
- } catch (InterruptedException ie) {
- LOG.error("ExpiredTokenRemover received " + ie);
- }
- }
- } catch (Throwable t) {
- LOG.error("ExpiredTokenRemover thread received unexpected exception",
- t);
- Runtime.getRuntime().exit(-1);
- }
- }
+ public AtomicInteger getCurrentKeyId() {
+ return currentKeyId;
+ }
+
+ public AtomicInteger getTokenSequenceNumber() {
+ return tokenSequenceNumber;
}
}
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenSecretManager.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenSecretManager.java
new file mode 100644
index 0000000000..469226693e
--- /dev/null
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenSecretManager.java
@@ -0,0 +1,146 @@
+/*
+ * 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.security;
+
+import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.AccessModeProto;
+import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
+import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.test.GenericTestUtils;
+import org.apache.hadoop.test.LambdaTestUtils;
+import org.apache.hadoop.util.Time;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.security.KeyPair;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.EnumSet;
+
+/**
+ * Test class for {@link OzoneBlockTokenSecretManager}.
+ */
+public class TestOzoneBlockTokenSecretManager {
+
+ private OzoneBlockTokenSecretManager secretManager;
+ private KeyPair keyPair;
+ private X509Certificate x509Certificate;
+ private long expiryTime;
+ private String omCertSerialId;
+ private static final String BASEDIR = GenericTestUtils
+ .getTempPath(TestOzoneBlockTokenSecretManager.class.getSimpleName());
+
+
+ @Before
+ public void setUp() throws Exception {
+ OzoneConfiguration conf = new OzoneConfiguration();
+ conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, BASEDIR);
+ // Create Ozone Master key pair.
+ keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
+ expiryTime = Time.monotonicNow() + 60 * 60 * 24;
+ // Create Ozone Master certificate (SCM CA issued cert) and key store.
+ x509Certificate = KeyStoreTestUtil
+ .generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA");
+ omCertSerialId = x509Certificate.getSerialNumber().toString();
+ secretManager = new OzoneBlockTokenSecretManager(conf,
+ expiryTime, omCertSerialId);
+ secretManager.start(keyPair);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ secretManager = null;
+ }
+
+ @Test
+ public void testGenerateToken() throws Exception {
+ Token token = secretManager.generateToken(
+ "101", EnumSet.allOf(AccessModeProto.class), 100);
+ OzoneBlockTokenIdentifier identifier =
+ OzoneBlockTokenIdentifier.readFieldsProtobuf(new DataInputStream(
+ new ByteArrayInputStream(token.getIdentifier())));
+ // Check basic details.
+ Assert.assertTrue(identifier.getBlockId().equals("101"));
+ Assert.assertTrue(identifier.getAccessModes().equals(EnumSet
+ .allOf(AccessModeProto.class)));
+ Assert.assertTrue(identifier.getOmCertSerialId().equals(omCertSerialId));
+
+ validateHash(token.getPassword(), token.getIdentifier());
+ }
+
+ @Test
+ public void testCreateIdentifierSuccess() throws Exception {
+ OzoneBlockTokenIdentifier btIdentifier = secretManager.createIdentifier(
+ "testUser", "101", EnumSet.allOf(AccessModeProto.class), 100);
+
+ // Check basic details.
+ Assert.assertTrue(btIdentifier.getOwnerId().equals("testUser"));
+ Assert.assertTrue(btIdentifier.getBlockId().equals("101"));
+ Assert.assertTrue(btIdentifier.getAccessModes().equals(EnumSet
+ .allOf(AccessModeProto.class)));
+ Assert.assertTrue(btIdentifier.getOmCertSerialId().equals(omCertSerialId));
+
+ byte[] hash = secretManager.createPassword(btIdentifier);
+ validateHash(hash, btIdentifier.getBytes());
+ }
+
+ /**
+ * Validate hash using public key of KeyPair.
+ * */
+ private void validateHash(byte[] hash, byte[] identifier) throws Exception {
+ Signature rsaSignature =
+ Signature.getInstance(secretManager.getDefaultSignatureAlgorithm());
+ rsaSignature.initVerify(keyPair.getPublic());
+ rsaSignature.update(identifier);
+ Assert.assertTrue(rsaSignature.verify(hash));
+ }
+
+ @Test
+ public void testCreateIdentifierFailure() throws Exception {
+ LambdaTestUtils.intercept(SecurityException.class,
+ "Ozone block token can't be created without owner and access mode "
+ + "information.", () -> {
+ secretManager.createIdentifier();
+ });
+ }
+
+ @Test
+ public void testRenewToken() throws Exception {
+ LambdaTestUtils.intercept(UnsupportedOperationException.class,
+ "Renew token operation is not supported for ozone block" +
+ " tokens.", () -> {
+ secretManager.renewToken(null, null);
+ });
+ }
+
+ @Test
+ public void testCancelToken() throws Exception {
+ LambdaTestUtils.intercept(UnsupportedOperationException.class,
+ "Cancel token operation is not supported for ozone block" +
+ " tokens.", () -> {
+ secretManager.cancelToken(null, null);
+ });
+ }
+}
\ No newline at end of file
diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneSecretManager.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java
similarity index 87%
rename from hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneSecretManager.java
rename to hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java
index e4a8f2b40c..37ad5cea69 100644
--- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneSecretManager.java
+++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java
@@ -18,10 +18,6 @@
package org.apache.hadoop.ozone.security;
-import java.io.File;
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.Signature;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdds.HddsConfigKeys;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -38,19 +34,25 @@
import org.junit.Before;
import org.junit.Test;
-/**
- * Test class for {@link OzoneSecretManager}.
- */
-public class TestOzoneSecretManager {
+import java.io.File;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.Signature;
- private OzoneSecretManager secretManager;
+/**
+ * Test class for {@link OzoneDelegationTokenSecretManager}.
+ */
+public class TestOzoneDelegationTokenSecretManager {
+
+ private OzoneDelegationTokenSecretManager
+ secretManager;
private SecurityConfig securityConfig;
private KeyPair keyPair;
private long expiryTime;
private Text serviceRpcAdd;
private OzoneConfiguration conf;
- private static final String BASEDIR = GenericTestUtils
- .getTempPath(TestOzoneSecretManager.class.getSimpleName());
+ private static final String BASEDIR = GenericTestUtils.getTempPath(
+ TestOzoneDelegationTokenSecretManager.class.getSimpleName());
private final static Text TEST_USER = new Text("testUser");
private long tokenMaxLifetime = 1000 * 20;
private long tokenRemoverScanInterval = 1000 * 20;
@@ -76,7 +78,7 @@ public void tearDown() throws IOException {
public void testCreateToken() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -94,7 +96,7 @@ public void testCreateToken() throws Exception {
public void testRenewTokenSuccess() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -110,7 +112,7 @@ public void testRenewTokenSuccess() throws Exception {
public void testRenewTokenFailure() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -127,7 +129,7 @@ public void testRenewTokenFailure() throws Exception {
public void testRenewTokenFailureMaxTime() throws Exception {
secretManager = createSecretManager(conf, 100,
100, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -145,7 +147,7 @@ public void testRenewTokenFailureMaxTime() throws Exception {
public void testRenewTokenFailureRenewalTime() throws Exception {
secretManager = createSecretManager(conf, 1000 * 10,
10, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -159,7 +161,7 @@ public void testRenewTokenFailureRenewalTime() throws Exception {
public void testCreateIdentifier() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
OzoneTokenIdentifier identifier = secretManager.createIdentifier();
// Check basic details.
Assert.assertTrue(identifier.getOwner().equals(new Text("")));
@@ -171,7 +173,7 @@ public void testCreateIdentifier() throws Exception {
public void testCancelTokenSuccess() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -182,7 +184,7 @@ public void testCancelTokenSuccess() throws Exception {
public void testCancelTokenFailure() throws Exception {
secretManager = createSecretManager(conf, tokenMaxLifetime,
expiryTime, tokenRemoverScanInterval);
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
Token token = secretManager.createToken(TEST_USER,
TEST_USER,
TEST_USER);
@@ -205,12 +207,12 @@ private void validateHash(byte[] hash, byte[] identifier) throws Exception {
}
/**
- * Create instance of {@link OzoneSecretManager}.
+ * Create instance of {@link OzoneDelegationTokenSecretManager}.
*/
- private OzoneSecretManager createSecretManager(
- OzoneConfiguration config, long tokenMaxLife, long expiry, long
- tokenRemoverScanTime) throws IOException {
- return new OzoneSecretManager<>(config, tokenMaxLife,
+ private OzoneDelegationTokenSecretManager
+ createSecretManager(OzoneConfiguration config, long tokenMaxLife,
+ long expiry, long tokenRemoverScanTime) throws IOException {
+ return new OzoneDelegationTokenSecretManager<>(config, tokenMaxLife,
expiry, tokenRemoverScanTime, serviceRpcAdd);
}
}
\ No newline at end of file
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index f4b85ef100..918adac7f6 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -47,9 +47,9 @@
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ozone.OzoneSecurityUtil;
+import org.apache.hadoop.ozone.security.OzoneSecretManager;
import org.apache.hadoop.ozone.security.OzoneSecurityException;
import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
-import org.apache.hadoop.ozone.security.OzoneSecretManager;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.util.MBeans;
@@ -95,6 +95,7 @@
import org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType;
import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
import org.apache.hadoop.ozone.security.acl.RequestContext;
+import org.apache.hadoop.ozone.security.OzoneDelegationTokenSecretManager;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
@@ -111,6 +112,7 @@
import org.slf4j.LoggerFactory;
import javax.management.ObjectName;
+import javax.ws.rs.HEAD;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
@@ -128,7 +130,6 @@
import java.util.Timer;
import java.util.TimerTask;
-import static org.apache.hadoop.ozone.security.OzoneSecurityException.ResultCodes.*;
import static org.apache.hadoop.hdds.HddsUtils.getScmAddressForBlockClients;
import static org.apache.hadoop.hdds.HddsUtils.getScmAddressForClients;
import static org.apache.hadoop.hdds.HddsUtils.isHddsEnabled;
@@ -178,8 +179,8 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
+ StartupOption.HELP.getName() + " ]\n";
private static final String OM_DAEMON = "om";
private static boolean securityEnabled = false;
- private static OzoneSecretManager secretManager;
- // TO DO: For testing purpose only, remove before commiting
+ private static OzoneDelegationTokenSecretManager
+ secretManager;
private KeyPair keyPair;
private CertificateClient certClient;
private static boolean testSecureOmFlag = false;
@@ -367,9 +368,8 @@ private File getMetricsStorageFile() {
}
- private OzoneSecretManager createSecretManager(
- OzoneConfiguration conf)
- throws IOException {
+ private OzoneDelegationTokenSecretManager createSecretManager(
+ OzoneConfiguration conf) throws IOException {
long tokenRemoverScanInterval =
conf.getTimeDuration(OMConfigKeys.DELEGATION_REMOVER_SCAN_INTERVAL_KEY,
OMConfigKeys.DELEGATION_REMOVER_SCAN_INTERVAL_DEFAULT,
@@ -383,9 +383,8 @@ private OzoneSecretManager createSecretManager(
OMConfigKeys.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT,
TimeUnit.MILLISECONDS);
Text omRpcAddressTxt = new Text(OmUtils.getOmRpcAddress(configuration));
-
- return new OzoneSecretManager(conf, tokenMaxLifetime, tokenRenewInterval,
- tokenRemoverScanInterval, omRpcAddressTxt);
+ return new OzoneDelegationTokenSecretManager<>(conf, tokenMaxLifetime,
+ tokenRenewInterval, tokenRemoverScanInterval, omRpcAddressTxt);
}
private void stopSecretManager() throws IOException {
@@ -400,7 +399,7 @@ private void startSecretManager() {
try {
readKeyPair();
LOG.info("Starting OM secret manager");
- secretManager.startThreads(keyPair);
+ secretManager.start(keyPair);
} catch (IOException e) {
// Inability to start secret manager
// can't be recovered from.
@@ -424,7 +423,8 @@ private void readKeyPair() throws OzoneSecurityException {
certClient.getPrivateKey(OM_DAEMON));
} catch (Exception e) {
throw new OzoneSecurityException("Error reading private file for "
- + "OzoneManager", e, OM_PUBLIC_PRIVATE_KEY_FILE_NOT_EXIST);
+ + "OzoneManager", e, OzoneSecurityException
+ .ResultCodes.OM_PUBLIC_PRIVATE_KEY_FILE_NOT_EXIST);
}
}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneManagerBlockToken.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneManagerBlockToken.java
new file mode 100644
index 0000000000..cb7caf3169
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneManagerBlockToken.java
@@ -0,0 +1,251 @@
+/*
+ * 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.security;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
+import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
+import org.apache.hadoop.test.GenericTestUtils;
+import org.apache.hadoop.util.Time;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test class for OzoneManagerDelegationToken.
+ */
+public class TestOzoneManagerBlockToken {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(TestOzoneManagerBlockToken.class);
+ private static final String BASEDIR = GenericTestUtils
+ .getTempPath(TestOzoneManagerBlockToken.class.getSimpleName());
+ private static final String KEYSTORES_DIR =
+ new File(BASEDIR).getAbsolutePath();
+ private static long expiryTime;
+ private static KeyPair keyPair;
+ private static X509Certificate cert;
+ private static final long MAX_LEN = 1000;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ File base = new File(BASEDIR);
+ FileUtil.fullyDelete(base);
+ base.mkdirs();
+ expiryTime = Time.monotonicNow() + 60 * 60 * 24;
+
+ // Create Ozone Master key pair.
+ keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
+ // Create Ozone Master certificate (SCM CA issued cert) and key store.
+ cert = KeyStoreTestUtil
+ .generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA");
+ }
+
+ @After
+ public void cleanUp() {
+ }
+
+ @Test
+ public void testSignToken() throws GeneralSecurityException, IOException {
+ String keystore = new File(KEYSTORES_DIR, "keystore.jks")
+ .getAbsolutePath();
+ String truststore = new File(KEYSTORES_DIR, "truststore.jks")
+ .getAbsolutePath();
+ String trustPassword = "trustPass";
+ String keyStorePassword = "keyStorePass";
+ String keyPassword = "keyPass";
+
+
+ KeyStoreTestUtil.createKeyStore(keystore, keyStorePassword, keyPassword,
+ "OzoneMaster", keyPair.getPrivate(), cert);
+
+ // Create trust store and put the certificate in the trust store
+ Map certs = Collections.singletonMap("server",
+ cert);
+ KeyStoreTestUtil.createTrustStore(truststore, trustPassword, certs);
+
+ // Sign the OzoneMaster Token with Ozone Master private key
+ PrivateKey privateKey = keyPair.getPrivate();
+ OzoneBlockTokenIdentifier tokenId = new OzoneBlockTokenIdentifier(
+ "testUser", "84940",
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString(), MAX_LEN);
+ byte[] signedToken = signTokenAsymmetric(tokenId, privateKey);
+
+ // Verify a valid signed OzoneMaster Token with Ozone Master
+ // public key(certificate)
+ boolean isValidToken = verifyTokenAsymmetric(tokenId, signedToken, cert);
+ LOG.info("{} is {}", tokenId, isValidToken ? "valid." : "invalid.");
+
+ // Verify an invalid signed OzoneMaster Token with Ozone Master
+ // public key(certificate)
+ tokenId = new OzoneBlockTokenIdentifier("", "",
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString(), MAX_LEN);
+ LOG.info("Unsigned token {} is {}", tokenId,
+ verifyTokenAsymmetric(tokenId, RandomUtils.nextBytes(128), cert));
+
+ }
+
+ public byte[] signTokenAsymmetric(OzoneBlockTokenIdentifier tokenId,
+ PrivateKey privateKey) throws NoSuchAlgorithmException,
+ InvalidKeyException, SignatureException {
+ Signature rsaSignature = Signature.getInstance("SHA256withRSA");
+ rsaSignature.initSign(privateKey);
+ rsaSignature.update(tokenId.getBytes());
+ byte[] signature = rsaSignature.sign();
+ return signature;
+ }
+
+ public boolean verifyTokenAsymmetric(OzoneBlockTokenIdentifier tokenId,
+ byte[] signature, Certificate certificate) throws InvalidKeyException,
+ NoSuchAlgorithmException, SignatureException {
+ Signature rsaSignature = Signature.getInstance("SHA256withRSA");
+ rsaSignature.initVerify(certificate);
+ rsaSignature.update(tokenId.getBytes());
+ boolean isValid = rsaSignature.verify(signature);
+ return isValid;
+ }
+
+ private byte[] signTokenSymmetric(OzoneBlockTokenIdentifier identifier,
+ Mac mac, SecretKey key) {
+ try {
+ mac.init(key);
+ } catch (InvalidKeyException ike) {
+ throw new IllegalArgumentException("Invalid key to HMAC computation",
+ ike);
+ }
+ return mac.doFinal(identifier.getBytes());
+ }
+
+ OzoneBlockTokenIdentifier generateTestToken() {
+ return new OzoneBlockTokenIdentifier(RandomStringUtils.randomAlphabetic(6),
+ RandomStringUtils.randomAlphabetic(5),
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString(), MAX_LEN);
+ }
+
+ @Test
+ public void testAsymmetricTokenPerf() throws NoSuchAlgorithmException,
+ CertificateEncodingException, NoSuchProviderException,
+ InvalidKeyException, SignatureException {
+ final int testTokenCount = 1000;
+ List tokenIds = new ArrayList<>();
+ List tokenPasswordAsym = new ArrayList<>();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenIds.add(generateTestToken());
+ }
+
+ KeyPair kp = KeyStoreTestUtil.generateKeyPair("RSA");
+
+ // Create Ozone Master certificate (SCM CA issued cert) and key store
+ X509Certificate omCert;
+ omCert = KeyStoreTestUtil.generateCertificate("CN=OzoneMaster",
+ kp, 30, "SHA256withRSA");
+
+ long startTime = Time.monotonicNowNanos();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenPasswordAsym.add(
+ signTokenAsymmetric(tokenIds.get(i), kp.getPrivate()));
+ }
+ long duration = Time.monotonicNowNanos() - startTime;
+ LOG.info("Average token sign time with HmacSha256(RSA/1024 key) is {} ns",
+ duration / testTokenCount);
+
+ startTime = Time.monotonicNowNanos();
+ for (int i = 0; i < testTokenCount; i++) {
+ verifyTokenAsymmetric(tokenIds.get(i), tokenPasswordAsym.get(i), omCert);
+ }
+ duration = Time.monotonicNowNanos() - startTime;
+ LOG.info("Average token verify time with HmacSha256(RSA/1024 key) "
+ + "is {} ns", duration / testTokenCount);
+ }
+
+ @Test
+ public void testSymmetricTokenPerf() {
+ String hmacSHA1 = "HmacSHA1";
+ String hmacSHA256 = "HmacSHA256";
+
+ testSymmetricTokenPerfHelper(hmacSHA1, 64);
+ testSymmetricTokenPerfHelper(hmacSHA256, 1024);
+ }
+
+ public void testSymmetricTokenPerfHelper(String hmacAlgorithm, int keyLen) {
+ final int testTokenCount = 1000;
+ List tokenIds = new ArrayList<>();
+ List tokenPasswordSym = new ArrayList<>();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenIds.add(generateTestToken());
+ }
+
+ KeyGenerator keyGen;
+ try {
+ keyGen = KeyGenerator.getInstance(hmacAlgorithm);
+ keyGen.init(keyLen);
+ } catch (NoSuchAlgorithmException nsa) {
+ throw new IllegalArgumentException("Can't find " + hmacAlgorithm +
+ " algorithm.");
+ }
+
+ Mac mac;
+ try {
+ mac = Mac.getInstance(hmacAlgorithm);
+ } catch (NoSuchAlgorithmException nsa) {
+ throw new IllegalArgumentException("Can't find " + hmacAlgorithm +
+ " algorithm.");
+ }
+
+ SecretKey secretKey = keyGen.generateKey();
+
+ long startTime = Time.monotonicNowNanos();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenPasswordSym.add(
+ signTokenSymmetric(tokenIds.get(i), mac, secretKey));
+ }
+ long duration = Time.monotonicNowNanos() - startTime;
+ LOG.info("Average token sign time with {}({} symmetric key) is {} ns",
+ hmacAlgorithm, keyLen, duration / testTokenCount);
+ }
+}
\ No newline at end of file