HDDS-8. Add OzoneManager Delegation Token support. Contributed by Ajay Kumar.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/ffe5e7de Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/ffe5e7de Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/ffe5e7de Branch: refs/heads/HDDS-4 Commit: ffe5e7de316a15e815e7fb98cdc0c906f1f16fd7 Parents: dea0b7b Author: Ajay Kumar <a...@apache.com> Authored: Thu Nov 15 12:18:19 2018 -0800 Committer: Ajay Kumar <a...@apache.com> Committed: Thu Nov 15 12:18:19 2018 -0800 ---------------------------------------------------------------------- .../apache/hadoop/ozone/OzoneConfigKeys.java | 4 + .../org/apache/hadoop/ozone/OzoneConsts.java | 1 + .../common/src/main/resources/ozone-default.xml | 33 + .../ozone/client/protocol/ClientProtocol.java | 32 + .../hadoop/ozone/client/rest/RestClient.java | 41 ++ .../hadoop/ozone/client/rpc/RpcClient.java | 41 ++ .../ozone/client/TestHddsClientUtils.java | 2 +- .../java/org/apache/hadoop/ozone/OmUtils.java | 17 +- .../apache/hadoop/ozone/om/OMConfigKeys.java | 13 + .../ozone/om/protocol/OzoneManagerProtocol.java | 2 +- .../protocol/OzoneManagerSecurityProtocol.java | 67 +++ ...neManagerProtocolClientSideTranslatorPB.java | 77 +++ .../om/protocolPB/OzoneManagerProtocolPB.java | 3 + .../hadoop/ozone/protocolPB/OMPBHelper.java | 38 ++ .../security/OzoneDelegationTokenSelector.java | 3 +- .../ozone/security/OzoneSecretManager.java | 598 +++++++++++++++++++ .../hadoop/ozone/security/OzoneSecretStore.java | 250 ++++++++ .../ozone/security/OzoneSecurityException.java | 104 ++++ .../src/main/proto/OzoneManagerProtocol.proto | 19 + .../ozone/security/TestOzoneSecretManager.java | 216 +++++++ .../hadoop/ozone/TestSecureOzoneCluster.java | 327 ++++++++-- .../apache/hadoop/ozone/om/OzoneManager.java | 273 ++++++++- ...neManagerProtocolServerSideTranslatorPB.java | 59 ++ 23 files changed, 2147 insertions(+), 73 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java index 4f7b3af..a735957 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java @@ -304,6 +304,10 @@ public final class OzoneConfigKeys { public static final String OZONE_CONTAINER_COPY_WORKDIR = "hdds.datanode.replication.work.dir"; + public static final String OZONE_MAX_KEY_LEN = + "ozone.max.key.len"; + public static final int OZONE_MAX_KEY_LEN_DEFAULT = 1024 * 1024; + /** * There is no need to instantiate this class. */ http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java index b77d621..9292c1e 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java @@ -99,6 +99,7 @@ public final class OzoneConsts { public static final String DN_CONTAINER_DB = "-dn-"+ CONTAINER_DB_SUFFIX; public static final String DELETED_BLOCK_DB = "deletedBlock.db"; public static final String OM_DB_NAME = "om.db"; + public static final String OZONE_MANAGER_TOKEN_DB_NAME = "om-token.db"; public static final String STORAGE_DIR_CHUNKS = "chunks"; http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-hdds/common/src/main/resources/ozone-default.xml ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 2053935..ab9633c 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -938,6 +938,15 @@ every principal specified in the keytab file. </description> </property> + <property> + <name>ozone.max.key.len</name> + <value>1048576</value> + <tag>OZONE, SECURITY</tag> + <description> + Maximum length of private key in Ozone. Used in Ozone delegation and + block tokens. + </description> + </property> <!--Client Settings--> <property> @@ -1431,4 +1440,28 @@ Name of file which stores public key generated for SCM CA. </description> </property> + <property> + <name>ozone.manager.delegation.remover.scan.interval</name> + <value>3600000</value> + <description> + Time interval after which ozone secret manger scans for expired + delegation token. + </description> + </property> + <property> + <name>ozone.manager.delegation.token.renew-interval</name> + <value>1d</value> + <description> + Default time interval after which ozone delegation token will + require renewal before any further use. + </description> + </property> + <property> + <name>ozone.manager.delegation.token.max-lifetime</name> + <value>7d</value> + <description> + Default max time interval after which ozone delegation token will + not be renewed. + </description> + </property> </configuration> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java index 50a7a19..4ffcfd2 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java @@ -20,6 +20,7 @@ package org.apache.hadoop.ozone.client.protocol; import org.apache.hadoop.hdds.protocol.StorageType; import org.apache.hadoop.hdds.scm.ScmConfigKeys; +import org.apache.hadoop.io.Text; import org.apache.hadoop.ozone.OzoneAcl; import org.apache.hadoop.ozone.client.*; import org.apache.hadoop.hdds.client.OzoneQuota; @@ -30,7 +31,9 @@ import org.apache.hadoop.ozone.client.io.OzoneOutputStream; import java.io.IOException; import java.util.List; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; import org.apache.hadoop.security.KerberosInfo; +import org.apache.hadoop.security.token.Token; /** * An implementer of this interface is capable of connecting to Ozone Cluster @@ -372,4 +375,33 @@ public interface ClientProtocol { */ void close() throws IOException; + /** + * Get a valid Delegation Token. + * + * @param renewer the designated renewer for the token + * @return Token<OzoneDelegationTokenSelector> + * @throws IOException + */ + Token<OzoneTokenIdentifier> getDelegationToken(Text renewer) + throws IOException; + + /** + * Renew an existing delegation token. + * + * @param token delegation token obtained earlier + * @return the new expiration time + * @throws IOException + */ + long renewDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException; + + /** + * Cancel an existing delegation token. + * + * @param token delegation token + * @throws IOException + */ + void cancelDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException; + } http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rest/RestClient.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rest/RestClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rest/RestClient.java index cc0c0f7..1442cda 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rest/RestClient.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rest/RestClient.java @@ -26,6 +26,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdds.protocol.StorageType; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.scm.client.HddsClientUtils; +import org.apache.hadoop.io.Text; import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.ozone.OzoneAcl; import org.apache.hadoop.ozone.OzoneConfigKeys; @@ -44,10 +45,12 @@ import org.apache.hadoop.ozone.client.rest.response.VolumeInfo; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.helpers.ServiceInfo; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServicePort; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; import org.apache.hadoop.ozone.web.response.ListBuckets; import org.apache.hadoop.ozone.web.response.ListKeys; import org.apache.hadoop.ozone.web.response.ListVolumes; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; import org.apache.hadoop.util.Time; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; @@ -667,6 +670,44 @@ public class RestClient implements ClientProtocol { } } + /** + * Get a valid Delegation Token. Not supported for RestClient. + * + * @param renewer the designated renewer for the token + * @return Token<OzoneDelegationTokenSelector> + * @throws IOException + */ + @Override + public Token<OzoneTokenIdentifier> getDelegationToken(Text renewer) + throws IOException { + throw new IOException("Method not supported"); + } + + /** + * Renew an existing delegation token. Not supported for RestClient. + * + * @param token delegation token obtained earlier + * @return the new expiration time + * @throws IOException + */ + @Override + public long renewDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + throw new IOException("Method not supported"); + } + + /** + * Cancel an existing delegation token. Not supported for RestClient. + * + * @param token delegation token + * @throws IOException + */ + @Override + public void cancelDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + throw new IOException("Method not supported"); + } + @Override public OzoneInputStream getKey( String volumeName, String bucketName, String keyName) http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java index 6fd573c..a77c2e2 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java @@ -64,7 +64,10 @@ import org.apache.hadoop.hdds.scm.protocolPB .StorageContainerLocationProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.scm.protocolPB .StorageContainerLocationProtocolPB; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.io.Text; import org.apache.logging.log4j.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -363,6 +366,44 @@ public class RpcClient implements ClientProtocol { ozoneManagerClient.setBucketProperty(builder.build()); } + /** + * Get a valid Delegation Token. + * + * @param renewer the designated renewer for the token + * @return Token<OzoneDelegationTokenSelector> + * @throws IOException + */ + @Override + public Token<OzoneTokenIdentifier> getDelegationToken(Text renewer) + throws IOException { + return ozoneManagerClient.getDelegationToken(renewer); + } + + /** + * Renew an existing delegation token. + * + * @param token delegation token obtained earlier + * @return the new expiration time + * @throws IOException + */ + @Override + public long renewDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + return ozoneManagerClient.renewDelegationToken(token); + } + + /** + * Cancel an existing delegation token. + * + * @param token delegation token + * @throws IOException + */ + @Override + public void cancelDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + ozoneManagerClient.cancelDelegationToken(token); + } + @Override public void setBucketVersioning( String volumeName, String bucketName, Boolean versioning) http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestHddsClientUtils.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestHddsClientUtils.java b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestHddsClientUtils.java index 9850778..79d4c49 100644 --- a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestHddsClientUtils.java +++ b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/TestHddsClientUtils.java @@ -83,7 +83,7 @@ public class TestHddsClientUtils { } @Test - public void testGetOmAddress() { + public void testgetOmSocketAddress() { final Configuration conf = new OzoneConfiguration(); // First try a client address with just a host name. Verify it falls http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java index 9280648..e7ed84f 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java @@ -54,14 +54,21 @@ public final class OmUtils { * @param conf * @return Target InetSocketAddress for the SCM service endpoint. */ - public static InetSocketAddress getOmAddress( - Configuration conf) { + public static InetSocketAddress getOmAddress(Configuration conf) { + return NetUtils.createSocketAddr(getOmRpcAddress(conf)); + } + + /** + * Retrieve the socket address that is used by OM. + * @param conf + * @return Target InetSocketAddress for the SCM service endpoint. + */ + public static String getOmRpcAddress(Configuration conf) { final Optional<String> host = getHostNameFromConfigKeys(conf, OZONE_OM_ADDRESS_KEY); - return NetUtils.createSocketAddr( - host.orElse(OZONE_OM_BIND_HOST_DEFAULT) + ":" + - getOmRpcPort(conf)); + return host.orElse(OZONE_OM_BIND_HOST_DEFAULT) + ":" + + getOmRpcPort(conf); } /** http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java index 313d969..fcd7ebe 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java @@ -90,4 +90,17 @@ public final class OMConfigKeys { "ozone.om.http.kerberos.keytab.file"; public static final String OZONE_OM_HTTP_KERBEROS_PRINCIPAL_KEY = "ozone.om.http.kerberos.principal"; + // Delegation token related keys + public static final String DELEGATION_REMOVER_SCAN_INTERVAL_KEY = + "ozone.manager.delegation.remover.scan.interval"; + public static final long DELEGATION_REMOVER_SCAN_INTERVAL_DEFAULT = + 60*60*1000; + public static final String DELEGATION_TOKEN_RENEW_INTERVAL_KEY = + "ozone.manager.delegation.token.renew-interval"; + public static final long DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT = + 24*60*60*1000; // 1 day = 86400000 ms + public static final String DELEGATION_TOKEN_MAX_LIFETIME_KEY = + "ozone.manager.delegation.token.max-lifetime"; + public static final long DELEGATION_TOKEN_MAX_LIFETIME_DEFAULT = + 7*24*60*60*1000; // 7 days } http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java index d4bcb41..659555a 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java @@ -37,7 +37,7 @@ import org.apache.hadoop.security.KerberosInfo; */ @KerberosInfo( serverPrincipal = OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY) -public interface OzoneManagerProtocol { +public interface OzoneManagerProtocol extends OzoneManagerSecurityProtocol { /** * Creates a volume. http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerSecurityProtocol.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerSecurityProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerSecurityProtocol.java new file mode 100644 index 0000000..15873d0 --- /dev/null +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerSecurityProtocol.java @@ -0,0 +1,67 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.om.protocol; + +import java.io.IOException; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.retry.Idempotent; +import org.apache.hadoop.ozone.om.OMConfigKeys; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; +import org.apache.hadoop.security.KerberosInfo; +import org.apache.hadoop.security.token.Token; + +/** + * Security protocol for a secure OzoneManager. + */ +@KerberosInfo( + serverPrincipal = OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY) +public interface OzoneManagerSecurityProtocol { + + /** + * Get a valid Delegation Token. + * + * @param renewer the designated renewer for the token + * @return Token<OzoneDelegationTokenSelector> + * @throws IOException + */ + @Idempotent + Token<OzoneTokenIdentifier> getDelegationToken(Text renewer) + throws IOException; + + /** + * Renew an existing delegation token. + * + * @param token delegation token obtained earlier + * @return the new expiration time + * @throws IOException + */ + @Idempotent + long renewDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException; + + /** + * Cancel an existing delegation token. + * + * @param token delegation token + * @throws IOException + */ + @Idempotent + void cancelDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException; + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java index 94c57e5..c7b06f2 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java @@ -23,6 +23,7 @@ import com.google.common.collect.Lists; import com.google.protobuf.RpcController; import com.google.protobuf.ServiceException; import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.io.Text; import org.apache.hadoop.ipc.ProtobufHelper; import org.apache.hadoop.ipc.ProtocolTranslator; import org.apache.hadoop.ozone.om.helpers.OmBucketArgs; @@ -135,6 +136,13 @@ import java.io.IOException; import java.util.List; import java.util.ArrayList; import java.util.stream.Collectors; +import org.apache.hadoop.ozone.protocolPB.OMPBHelper; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; +import org.apache.hadoop.security.proto.SecurityProtos.CancelDelegationTokenRequestProto; +import org.apache.hadoop.security.proto.SecurityProtos.GetDelegationTokenRequestProto; +import org.apache.hadoop.security.proto.SecurityProtos.GetDelegationTokenResponseProto; +import org.apache.hadoop.security.proto.SecurityProtos.RenewDelegationTokenRequestProto; +import org.apache.hadoop.security.token.Token; /** * The client side implementation of OzoneManagerProtocol. @@ -846,4 +854,73 @@ public final class OzoneManagerProtocolClientSideTranslatorPB public Object getUnderlyingProxyObject() { return null; } + + /** + * Get a valid Delegation Token. + * + * @param renewer the designated renewer for the token + * @return Token<OzoneDelegationTokenSelector> + * @throws IOException + */ + @Override + public Token<OzoneTokenIdentifier> getDelegationToken(Text renewer) + throws IOException { + GetDelegationTokenRequestProto req = GetDelegationTokenRequestProto + .newBuilder() + .setRenewer(renewer == null ? "" : renewer.toString()) + .build(); + try { + GetDelegationTokenResponseProto resp = + rpcProxy.getDelegationToken(NULL_RPC_CONTROLLER, req); + return resp.hasToken() ? + OMPBHelper.convertToDelegationToken(resp.getToken()) : null; + } catch (ServiceException e) { + throw ProtobufHelper.getRemoteException(e); + } catch (Exception e){ + e.printStackTrace(); + throw e; + } + } + + /** + * Renew an existing delegation token. + * + * @param token delegation token obtained earlier + * @return the new expiration time + * @throws IOException + */ + @Override + public long renewDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + RenewDelegationTokenRequestProto req = + RenewDelegationTokenRequestProto.newBuilder(). + setToken(OMPBHelper.convertToTokenProto(token)). + build(); + try { + return rpcProxy.renewDelegationToken(NULL_RPC_CONTROLLER, req) + .getNewExpiryTime(); + } catch (ServiceException e) { + throw ProtobufHelper.getRemoteException(e); + } + } + + /** + * Cancel an existing delegation token. + * + * @param token delegation token + * @throws IOException + */ + @Override + public void cancelDelegationToken(Token<OzoneTokenIdentifier> token) + throws IOException { + CancelDelegationTokenRequestProto req = CancelDelegationTokenRequestProto + .newBuilder() + .setToken(OMPBHelper.convertToTokenProto(token)) + .build(); + try { + rpcProxy.cancelDelegationToken(NULL_RPC_CONTROLLER, req); + } catch (ServiceException e) { + throw ProtobufHelper.getRemoteException(e); + } + } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolPB.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolPB.java index 27e8f22..175527b 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolPB.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolPB.java @@ -23,6 +23,8 @@ import org.apache.hadoop.ipc.ProtocolInfo; import org.apache.hadoop.ozone.protocol.proto .OzoneManagerProtocolProtos.OzoneManagerService; import org.apache.hadoop.security.KerberosInfo; +import org.apache.hadoop.security.token.TokenInfo; +import org.apache.hadoop.ozone.security.OzoneDelegationTokenSelector; /** * Protocol used to communicate with OM. @@ -32,6 +34,7 @@ import org.apache.hadoop.security.KerberosInfo; protocolVersion = 1) @KerberosInfo( serverPrincipal = OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY) +@TokenInfo(OzoneDelegationTokenSelector.class) @InterfaceAudience.Private public interface OzoneManagerProtocolPB extends OzoneManagerService.BlockingInterface { http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/protocolPB/OMPBHelper.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/protocolPB/OMPBHelper.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/protocolPB/OMPBHelper.java index d57d32e..df069ce 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/protocolPB/OMPBHelper.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/protocolPB/OMPBHelper.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.ozone.protocolPB; +import com.google.protobuf.ByteString; +import org.apache.hadoop.io.Text; import org.apache.hadoop.ozone.OzoneAcl; import org.apache.hadoop.ozone.protocol.proto .OzoneManagerProtocolProtos.OzoneAclInfo; @@ -24,6 +26,9 @@ import org.apache.hadoop.ozone.protocol.proto .OzoneManagerProtocolProtos.OzoneAclInfo.OzoneAclType; import org.apache.hadoop.ozone.protocol.proto .OzoneManagerProtocolProtos.OzoneAclInfo.OzoneAclRights; +import org.apache.hadoop.ozone.security.OzoneTokenIdentifier; +import org.apache.hadoop.security.proto.SecurityProtos.TokenProto; +import org.apache.hadoop.security.token.Token; /** * Utilities for converting protobuf classes. @@ -110,4 +115,37 @@ public final class OMPBHelper { return new OzoneAcl(aclType, aclInfo.getName(), aclRights); } + + /** + * Converts Ozone delegation token to @{@link TokenProto}. + * @return tokenProto + */ + public static TokenProto convertToTokenProto(Token<?> tok) { + if(tok == null){ + throw new IllegalArgumentException("Invalid argument: token is null"); + } + + return TokenProto.newBuilder(). + setIdentifier(getByteString(tok.getIdentifier())). + setPassword(getByteString(tok.getPassword())). + setKind(tok.getKind().toString()). + setService(tok.getService().toString()).build(); + } + + public static ByteString getByteString(byte[] bytes) { + // return singleton to reduce object allocation + return (bytes.length == 0) ? ByteString.EMPTY : ByteString.copyFrom(bytes); + } + + /** + * Converts @{@link TokenProto} to Ozone delegation token. + * + * @return Ozone + */ + public static Token<OzoneTokenIdentifier> convertToDelegationToken( + TokenProto tokenProto) { + return new Token<>(tokenProto.getIdentifier() + .toByteArray(), tokenProto.getPassword().toByteArray(), new Text( + tokenProto.getKind()), new Text(tokenProto.getService())); + } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java index 0d63c4e..8e35f22 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java @@ -19,7 +19,6 @@ package org.apache.hadoop.ozone.security; import java.util.Collection; import org.apache.hadoop.classification.InterfaceAudience; -import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -32,7 +31,7 @@ import org.slf4j.LoggerFactory; */ @InterfaceAudience.Private public class OzoneDelegationTokenSelector - extends AbstractDelegationTokenSelector<DelegationTokenIdentifier> { + extends AbstractDelegationTokenSelector<OzoneTokenIdentifier> { public OzoneDelegationTokenSelector() { super(OzoneTokenIdentifier.KIND_NAME); http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..0c84404 --- /dev/null +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretManager.java @@ -0,0 +1,598 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 com.google.common.base.Preconditions; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +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 + * private key, + */ +@InterfaceAudience.Private +@InterfaceStability.Unstable +public class OzoneSecretManager<T extends OzoneTokenIdentifier> + extends SecretManager<T> { + + private static final Logger LOG = LoggerFactory + .getLogger(OzoneSecretManager.class); + /** + * The name of the Private/Public Key based hashing algorithm. + */ + private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; + private final long tokenMaxLifetime; + private final long tokenRenewInterval; + private final long tokenRemoverScanInterval; + private final Text service; + private final Map<Integer, OzoneSecretKey> allKeys; + private final Map<T, TokenInfo> 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; + + /** + * 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 OzoneSecretManager(OzoneConfiguration conf, long tokenMaxLifetime, + long tokenRenewInterval, long dtRemoverScanInterval, Text service) + throws IOException { + 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()); + this.service = service; + this.maxKeyLength = conf.getInt(OzoneConfigKeys.OZONE_MAX_KEY_LEN, + OzoneConfigKeys.OZONE_MAX_KEY_LEN_DEFAULT); + } + + @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<T> 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<T> 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 + * as password. + * + * @param identifier + * @param privateKey + * @return byte[] signed byte array + */ + public byte[] createPassword(byte[] identifier, PrivateKey privateKey) + throws OzoneSecurityException { + try { + Signature rsaSignature = Signature.getInstance( + DEFAULT_SIGNATURE_ALGORITHM); + rsaSignature.initSign(privateKey); + rsaSignature.update(identifier); + return rsaSignature.sign(); + } catch (InvalidKeyException | NoSuchAlgorithmException | + SignatureException ex) { + throw new OzoneSecurityException("Error while creating HMAC hash for " + + "token.", ex, OzoneSecurityException.ResultCodes + .SECRET_MANAGER_HMAC_ERROR); + } + } + + @Override + public byte[] createPassword(T identifier) { + LOG.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), + ioe); + } + return password; + } + + @Override + public byte[] retrievePassword(T identifier) throws InvalidToken { + return checkToken(identifier).getPassword(); + } + + /** + * 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 + */ + public synchronized long renewToken(Token<T> 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; + } + + /** + * 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<T> 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 int incrementCurrentKeyId() { + return currentKeyId.incrementAndGet(); + } + + public int getDelegationTokenSeqNum() { + return tokenSequenceNumber.get(); + } + + public void setDelegationTokenSeqNum(int seqNum) { + tokenSequenceNumber.set(seqNum); + } + + public int incrementDelegationTokenSeqNum() { + return tokenSequenceNumber.incrementAndGet(); + } + + /** + * Validates if given token is valid. + * + * @param identifier + * @param password + */ + private boolean validateToken(T identifier, byte[] password) { + try { + Signature rsaSignature = Signature.getInstance("SHA256withRSA"); + rsaSignature.initVerify(currentKey.getPublicKey()); + rsaSignature.update(identifier.getBytes()); + return rsaSignature.verify(password); + } catch (NoSuchAlgorithmException | SignatureException | + InvalidKeyException e) { + return false; + } + } + + /** + * 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<Map.Entry<Integer, OzoneSecretKey>> it = allKeys.entrySet() + .iterator(); it.hasNext();) { + Map.Entry<Integer, OzoneSecretKey> 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<T> state) + throws IOException { + LOG.info("Loading token state into token manager."); + for (OzoneSecretKey key : state.ozoneManagerSecretState()) { + allKeys.putIfAbsent(key.getKeyId(), key); + } + for (Map.Entry<T, Long> entry : state.getTokenState().entrySet()) { + addPersistedDelegationToken(entry.getKey(), entry.getValue()); + } + } + + private 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. + */ + public void startThreads(KeyPair keyPair) throws IOException { + Preconditions.checkState(!running); + 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); + } + } + } + + /** + * Stops the OzoneSecretManager. + * + * @throws IOException + */ + public void stop() throws IOException { + stopThreads(); + if (this.store != null) { + this.store.close(); + } + } + + /** + * 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; + } + } + + /** + * Remove expired delegation tokens from cache and persisted store. + */ + private void removeExpiredToken() throws IOException { + long now = Time.monotonicNow(); + synchronized (this) { + Iterator<Map.Entry<T, + TokenInfo>> i = currentTokens.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry<T, + TokenInfo> entry = i.next(); + long renewDate = entry.getValue().getRenewDate(); + if (renewDate < now) { + i.remove(); + store.removeToken(entry.getKey()); + } + } + } + } + + /** + * Is Secret Manager running. + * + * @return true if secret mgr is running + */ + 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"); + } + } + + private class ExpiredTokenRemover extends Thread { + private long lastTokenCacheCleanup; + + @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); + } + } + } +} + http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretStore.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretStore.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretStore.java new file mode 100644 index 0000000..6528bcf --- /dev/null +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretStore.java @@ -0,0 +1,250 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.conf.OzoneConfiguration; +import org.apache.hadoop.hdfs.DFSUtil; +import org.apache.hadoop.io.IOUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.utils.MetadataKeyFilters; +import org.apache.hadoop.utils.MetadataStore; +import org.apache.hadoop.utils.MetadataStoreBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.apache.hadoop.hdds.server.ServerUtils.getOzoneMetaDirPath; +import static org.apache.hadoop.ozone.OzoneConsts.OZONE_MANAGER_TOKEN_DB_NAME; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_CACHE_SIZE_DEFAULT; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_DB_CACHE_SIZE_MB; + +/** + * SecretStore for Ozone Master. + */ +public class OzoneSecretStore<T extends OzoneTokenIdentifier> + implements Closeable { + + private static final Logger LOG = LoggerFactory + .getLogger(OzoneSecretStore.class); + private static final String TOKEN_MASTER_KEY_KEY_PREFIX = "tokens/key_"; + private static final String TOKEN_STATE_KEY_PREFIX = "tokens/token_"; + + @Override + public void close() throws IOException { + if (store != null) { + store.close(); + } + } + + + /** + * Support class to maintain state of OzoneSecretStore. + */ + public static class OzoneManagerSecretState<T> { + + private Map<T, Long> tokenState = new HashMap<>(); + private Set<OzoneSecretKey> tokenMasterKeyState = new HashSet<>(); + + public Map<T, Long> getTokenState() { + return tokenState; + } + + public Set<OzoneSecretKey> ozoneManagerSecretState() { + return tokenMasterKeyState; + } + } + + private MetadataStore store; + + public OzoneSecretStore(OzoneConfiguration conf) + throws IOException { + File metaDir = getOzoneMetaDirPath(conf); + final int cacheSize = conf.getInt(OZONE_OM_DB_CACHE_SIZE_MB, + OZONE_OM_DB_CACHE_SIZE_DEFAULT); + File omTokenDBFile = new File(metaDir.getPath(), + OZONE_MANAGER_TOKEN_DB_NAME); + this.store = MetadataStoreBuilder.newBuilder() + .setConf(conf) + .setDbFile(omTokenDBFile) + .setCacheSize(cacheSize * OzoneConsts.MB) + .build(); + } + + public OzoneManagerSecretState loadState() throws IOException { + OzoneManagerSecretState state = new OzoneManagerSecretState(); + int numKeys = loadMasterKeys(state); + LOG.info("Loaded " + numKeys + " token master keys"); + int numTokens = loadTokens(state); + LOG.info("Loaded " + numTokens + " tokens"); + return state; + } + + public void storeTokenMasterKey(OzoneSecretKey key) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Storing master key " + key.getKeyId()); + } + ByteArrayOutputStream memStream = new ByteArrayOutputStream(); + DataOutputStream dataStream = new DataOutputStream(memStream); + try { + key.write(dataStream); + dataStream.close(); + dataStream = null; + } finally { + IOUtils.cleanupWithLogger(LOG, dataStream); + } + try { + byte[] dbKey = getMasterKeyDBKey(key); + store.put(dbKey, memStream.toByteArray()); + } catch (IOException e) { + LOG.error("Unable to store master key " + key.getKeyId(), e); + throw e; + } + } + + + public void removeTokenMasterKey(OzoneSecretKey key) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Removing master key " + key.getKeyId()); + } + + byte[] dbKey = getMasterKeyDBKey(key); + try { + store.delete(dbKey); + } catch (IOException e) { + LOG.error("Unable to delete master key " + key.getKeyId(), e); + throw e; + } + } + + public void storeToken(T tokenId, Long renewDate) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Storing token " + tokenId.getSequenceNumber()); + } + + ByteArrayOutputStream memStream = new ByteArrayOutputStream(); + DataOutputStream dataStream = new DataOutputStream(memStream); + try { + tokenId.write(dataStream); + dataStream.writeLong(renewDate); + dataStream.close(); + dataStream = null; + } finally { + IOUtils.cleanupWithLogger(LOG, dataStream); + } + + byte[] dbKey = getTokenDBKey(tokenId); + try { + store.put(dbKey, memStream.toByteArray()); + } catch (IOException e) { + LOG.error("Unable to store token " + tokenId.toString(), e); + throw e; + } + } + + public void updateToken(T tokenId, Long renewDate) + throws IOException { + storeToken(tokenId, renewDate); + } + + public void removeToken(T tokenId) + throws IOException { + byte[] dbKey = getTokenDBKey(tokenId); + try { + store.delete(dbKey); + } catch (IOException e) { + LOG.error("Unable to remove token " + tokenId.toString(), e); + throw e; + } + } + + public int loadMasterKeys(OzoneManagerSecretState state) throws IOException { + MetadataKeyFilters.MetadataKeyFilter filter = + (preKey, currentKey, nextKey) -> DFSUtil.bytes2String(currentKey) + .startsWith(TOKEN_MASTER_KEY_KEY_PREFIX); + List<Map.Entry<byte[], byte[]>> kvs = store + .getRangeKVs(null, Integer.MAX_VALUE, filter); + kvs.forEach(entry -> { + try { + loadTokenMasterKey(state, entry.getValue()); + } catch (IOException e) { + LOG.warn("Failed to load master key ", + DFSUtil.bytes2String(entry.getKey()), e); + } + }); + return kvs.size(); + } + + private void loadTokenMasterKey(OzoneManagerSecretState state, byte[] data) + throws IOException { + OzoneSecretKey key = OzoneSecretKey.readProtoBuf(data); + state.tokenMasterKeyState.add(key); + } + + public int loadTokens(OzoneManagerSecretState state) throws IOException { + MetadataKeyFilters.MetadataKeyFilter filter = + (preKey, currentKey, nextKey) -> DFSUtil.bytes2String(currentKey) + .startsWith(TOKEN_STATE_KEY_PREFIX); + List<Map.Entry<byte[], byte[]>> kvs = + store.getRangeKVs(null, Integer.MAX_VALUE, filter); + kvs.forEach(entry -> { + try { + loadToken(state, entry.getValue()); + } catch (IOException e) { + LOG.warn("Failed to load token ", + DFSUtil.bytes2String(entry.getKey()), e); + } + }); + return kvs.size(); + } + + private void loadToken(OzoneManagerSecretState state, byte[] data) + throws IOException { + long renewDate; + DataInputStream in = new DataInputStream(new ByteArrayInputStream(data)); + T tokenId = (T) T.readProtoBuf(in); + try { + tokenId.readFields(in); + renewDate = in.readLong(); + } finally { + IOUtils.cleanupWithLogger(LOG, in); + } + state.tokenState.put(tokenId, renewDate); + } + + private byte[] getMasterKeyDBKey(OzoneSecretKey masterKey) { + return DFSUtil.string2Bytes( + TOKEN_MASTER_KEY_KEY_PREFIX + masterKey.getKeyId()); + } + + private byte[] getTokenDBKey(T tokenId) { + return DFSUtil.string2Bytes( + TOKEN_STATE_KEY_PREFIX + tokenId.getSequenceNumber()); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecurityException.java ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecurityException.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecurityException.java new file mode 100644 index 0000000..66533f3 --- /dev/null +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecurityException.java @@ -0,0 +1,104 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 java.io.IOException; + +/** + * Security exceptions thrown at Ozone layer. + */ +public class OzoneSecurityException extends IOException { + private final OzoneSecurityException.ResultCodes result; + + /** + * Constructs an {@code IOException} with {@code null} + * as its error detail message. + */ + public OzoneSecurityException(OzoneSecurityException.ResultCodes result) { + this.result = result; + } + + /** + * Constructs an {@code IOException} with the specified detail message. + * + * @param message The detail message (which is saved for later retrieval by + * the + * {@link #getMessage()} method) + */ + public OzoneSecurityException(String message, + OzoneSecurityException.ResultCodes result) { + super(message); + this.result = result; + } + + /** + * Constructs an {@code IOException} with the specified detail message + * and cause. + * <p> + * <p> Note that the detail message associated with {@code cause} is + * <i>not</i> automatically incorporated into this exception's detail + * message. + * + * @param message The detail message (which is saved for later retrieval by + * the + * {@link #getMessage()} method) + * @param cause The cause (which is saved for later retrieval by the {@link + * #getCause()} method). (A null value is permitted, and indicates that the + * cause is nonexistent or unknown.) + * @since 1.6 + */ + public OzoneSecurityException(String message, Throwable cause, + OzoneSecurityException.ResultCodes result) { + super(message, cause); + this.result = result; + } + + /** + * Constructs an {@code IOException} with the specified cause and a + * detail message of {@code (cause==null ? null : cause.toString())} + * (which typically contains the class and detail message of {@code cause}). + * This constructor is useful for IO exceptions that are little more + * than wrappers for other throwables. + * + * @param cause The cause (which is saved for later retrieval by the {@link + * #getCause()} method). (A null value is permitted, and indicates that the + * cause is nonexistent or unknown.) + * @since 1.6 + */ + public OzoneSecurityException(Throwable cause, + OzoneSecurityException.ResultCodes result) { + super(cause); + this.result = result; + } + + /** + * Returns resultCode. + * @return ResultCode + */ + public OzoneSecurityException.ResultCodes getResult() { + return result; + } + + /** + * Error codes to make it easy to decode these exceptions. + */ + public enum ResultCodes { + OM_PUBLIC_PRIVATE_KEY_FILE_NOT_EXIST, + SECRET_MANAGER_HMAC_ERROR + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto ---------------------------------------------------------------------- diff --git a/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto b/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto index 7b7cd64..5b62900 100644 --- a/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto +++ b/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto @@ -35,6 +35,7 @@ This is similar to Namenode for Ozone. */ import "hdds.proto"; +import "Security.proto"; enum Status { OK = 1; @@ -533,6 +534,24 @@ service OzoneManagerService { rpc getServiceList(ServiceListRequest) returns(ServiceListResponse); + + /** + Returns delegation token signed by OzoneManager. + */ + rpc getDelegationToken(hadoop.common.GetDelegationTokenRequestProto) + returns(hadoop.common.GetDelegationTokenResponseProto); + + /** + Renew delegation token signed by OzoneManager. + */ + rpc renewDelegationToken(hadoop.common.RenewDelegationTokenRequestProto) + returns(hadoop.common.RenewDelegationTokenResponseProto); + /** + Cancels a delegation token signed by OzoneManager. + */ + rpc cancelDelegationToken(hadoop.common.CancelDelegationTokenRequestProto) + returns(hadoop.common.CancelDelegationTokenResponseProto); + /** Creates an S3 bucket and creates an ozone mapping for that bucket. */ http://git-wip-us.apache.org/repos/asf/hadoop/blob/ffe5e7de/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneSecretManager.java ---------------------------------------------------------------------- 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/TestOzoneSecretManager.java new file mode 100644 index 0000000..e4a8f2b --- /dev/null +++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/TestOzoneSecretManager.java @@ -0,0 +1,216 @@ +/* + * 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 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; +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.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; + +/** + * Test class for {@link OzoneSecretManager}. + */ +public class TestOzoneSecretManager { + + private OzoneSecretManager<OzoneTokenIdentifier> 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 final static Text TEST_USER = new Text("testUser"); + private long tokenMaxLifetime = 1000 * 20; + private long tokenRemoverScanInterval = 1000 * 20; + + @Before + public void setUp() throws Exception { + conf = new OzoneConfiguration(); + conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, BASEDIR); + securityConfig = new SecurityConfig(conf); + // Create Ozone Master key pair. + keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); + expiryTime = Time.monotonicNow() + 60 * 60 * 24; + serviceRpcAdd = new Text("localhost"); + } + + @After + public void tearDown() throws IOException { + secretManager.stop(); + FileUtils.deleteQuietly(new File(BASEDIR)); + } + + @Test + public void testCreateToken() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + OzoneTokenIdentifier identifier = + OzoneTokenIdentifier.readProtoBuf(token.getIdentifier()); + // Check basic details. + Assert.assertTrue(identifier.getRealUser().equals(TEST_USER)); + Assert.assertTrue(identifier.getRenewer().equals(TEST_USER)); + Assert.assertTrue(identifier.getOwner().equals(TEST_USER)); + + validateHash(token.getPassword(), token.getIdentifier()); + } + + @Test + public void testRenewTokenSuccess() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + Thread.sleep(10 * 5); + long renewalTime = secretManager.renewToken(token, TEST_USER.toString()); + Assert.assertTrue(renewalTime > 0); + } + + /** + * Tests failure for mismatch in renewer. + */ + @Test + public void testRenewTokenFailure() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + LambdaTestUtils.intercept(AccessControlException.class, + "rougeUser tries to renew a token", () -> { + secretManager.renewToken(token, "rougeUser"); + }); + } + + /** + * Tests token renew failure due to max time. + */ + @Test + public void testRenewTokenFailureMaxTime() throws Exception { + secretManager = createSecretManager(conf, 100, + 100, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + Thread.sleep(101); + LambdaTestUtils.intercept(IOException.class, + "testUser tried to renew an expired token", () -> { + secretManager.renewToken(token, TEST_USER.toString()); + }); + } + + /** + * Tests token renew failure due to renewal time. + */ + @Test + public void testRenewTokenFailureRenewalTime() throws Exception { + secretManager = createSecretManager(conf, 1000 * 10, + 10, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + Thread.sleep(15); + LambdaTestUtils.intercept(IOException.class, "is expired", () -> { + secretManager.renewToken(token, TEST_USER.toString()); + }); + } + + @Test + public void testCreateIdentifier() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + OzoneTokenIdentifier identifier = secretManager.createIdentifier(); + // Check basic details. + Assert.assertTrue(identifier.getOwner().equals(new Text(""))); + Assert.assertTrue(identifier.getRealUser().equals(new Text(""))); + Assert.assertTrue(identifier.getRenewer().equals(new Text(""))); + } + + @Test + public void testCancelTokenSuccess() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + secretManager.cancelToken(token, TEST_USER.toString()); + } + + @Test + public void testCancelTokenFailure() throws Exception { + secretManager = createSecretManager(conf, tokenMaxLifetime, + expiryTime, tokenRemoverScanInterval); + secretManager.startThreads(keyPair); + Token<OzoneTokenIdentifier> token = secretManager.createToken(TEST_USER, + TEST_USER, + TEST_USER); + LambdaTestUtils.intercept(AccessControlException.class, + "rougeUser is not authorized to cancel the token", () -> { + secretManager.cancelToken(token, "rougeUser"); + }); + } + + /** + * Validate hash using public key of KeyPair. + */ + private void validateHash(byte[] hash, byte[] identifier) throws Exception { + Signature rsaSignature = + Signature.getInstance(securityConfig.getSignatureAlgo(), + securityConfig.getProvider()); + rsaSignature.initVerify(keyPair.getPublic()); + rsaSignature.update(identifier); + Assert.assertTrue(rsaSignature.verify(hash)); + } + + /** + * Create instance of {@link OzoneSecretManager}. + */ + private OzoneSecretManager<OzoneTokenIdentifier> createSecretManager( + OzoneConfiguration config, long tokenMaxLife, long expiry, long + tokenRemoverScanTime) throws IOException { + return new OzoneSecretManager<>(config, tokenMaxLife, + expiry, tokenRemoverScanTime, serviceRpcAdd); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org