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 efaeb2367640..ee5912d302d6 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 @@ -323,6 +323,7 @@ private OzoneConsts() { public static final String MAX_PARTS = "maxParts"; public static final String S3_BUCKET = "s3Bucket"; public static final String S3_GETSECRET_USER = "S3GetSecretUser"; + public static final String S3_REVOKESECRET_USER = "S3RevokeSecretUser"; public static final String RENAMED_KEYS_MAP = "renamedKeysMap"; public static final String UNRENAMED_KEYS_MAP = "unRenamedKeysMap"; public static final String MULTIPART_UPLOAD_PART_NUMBER = "partNumber"; diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java index fd3d214a90db..1f819ac91f92 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java @@ -156,6 +156,10 @@ public S3SecretValue getS3Secret(String kerberosID) throws IOException { return proxy.getS3Secret(kerberosID); } + public void revokeS3Secret(String kerberosID) throws IOException { + proxy.revokeS3Secret(kerberosID); + } + /** * Returns Iterator to iterate over all the volumes in object store. * The result can be restricted using volume prefix, will return all 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 cc9335506d70..4e0d7e185a8b 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 @@ -508,6 +508,13 @@ void cancelDelegationToken(Token token) */ S3SecretValue getS3Secret(String kerberosID) throws IOException; + /** + * Revoke S3 Secret of given kerberos user. + * @param kerberosID + * @throws IOException + */ + void revokeS3Secret(String kerberosID) throws IOException; + /** * Get KMS client provider. * @return KMS client provider. 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 35b97fbde2fa..9c16a8517e6c 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 @@ -606,6 +606,17 @@ public S3SecretValue getS3Secret(String kerberosID) throws IOException { return ozoneManagerClient.getS3Secret(kerberosID); } + /** + * {@inheritDoc} + */ + @Override + public void revokeS3Secret(String kerberosID) throws IOException { + Preconditions.checkArgument(Strings.isNotBlank(kerberosID), + "kerberosID cannot be null or empty."); + + ozoneManagerClient.revokeS3Secret(kerberosID); + } + @Override public void setBucketVersioning( String volumeName, String bucketName, Boolean versioning) 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 c389becf6b8f..577146db8cde 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 @@ -289,6 +289,7 @@ public static boolean isReadOnly( case PurgeKeys: case RecoverTrash: case DeleteOpenKeys: + case RevokeS3Secret: return false; default: LOG.error("CmdType {} is not categorized as readOnly or not.", cmdType); diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java index 3480063d1323..f969e75e0fd7 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java @@ -70,7 +70,8 @@ public enum OMAction implements AuditAction { LOOKUP_FILE, LIST_STATUS, - GET_S3_SECRET; + GET_S3_SECRET, + REVOKE_S3_SECRET; @Override public String getAction() { 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 68bfa4f9d8fa..183757972c30 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 @@ -372,6 +372,7 @@ OmMultipartUploadListParts listParts(String volumeName, String bucketName, */ OmMultipartUploadList listMultipartUploads(String volumeName, String bucketName, String prefix) throws IOException; + /** * Gets s3Secret for given kerberos user. * @param kerberosID @@ -380,6 +381,13 @@ OmMultipartUploadList listMultipartUploads(String volumeName, */ S3SecretValue getS3Secret(String kerberosID) throws IOException; + /** + * Revokes s3Secret of given kerberos user. + * @param kerberosID + * @throws IOException + */ + void revokeS3Secret(String kerberosID) throws IOException; + /** * OzoneFS api to get file status for an entry. * 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 43d72b90dfda..b3d3514cfccf 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 @@ -128,6 +128,7 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RenameKeyRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RenameKeysRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RenewDelegationTokenResponseProto; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RevokeS3SecretRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServiceListRequest; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServiceListResponse; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetAclRequest; @@ -848,7 +849,17 @@ public S3SecretValue getS3Secret(String kerberosID) throws IOException { .getGetS3SecretResponse(); return S3SecretValue.fromProtobuf(resp.getS3Secret()); + } + @Override + public void revokeS3Secret(String kerberosID) throws IOException { + RevokeS3SecretRequest request = RevokeS3SecretRequest.newBuilder() + .setKerberosID(kerberosID) + .build(); + OMRequest omRequest = createOMRequest(Type.RevokeS3Secret) + .setRevokeS3SecretRequest(request) + .build(); + handleError(submitRequest(omRequest)); } /** diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java index 4de9684f8664..7762990efd62 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java @@ -118,6 +118,7 @@ import org.junit.After; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -615,7 +616,7 @@ private void setupOm(OzoneConfiguration config) throws Exception { } @Test - public void testGetS3Secret() throws Exception { + public void testGetS3SecretAndRevokeS3Secret() throws Exception { // Setup secure OM for start setupOm(conf); @@ -643,6 +644,17 @@ public void testGetS3Secret() throws Exception { //access key fetched on both attempts must be same assertEquals(attempt1.getAwsAccessKey(), attempt2.getAwsAccessKey()); + // Revoke the existing secret + omClient.revokeS3Secret(username); + + // Get a new secret + S3SecretValue attempt3 = omClient.getS3Secret(username); + + // secret should differ because it has been revoked previously + assertNotEquals(attempt3.getAwsSecret(), attempt2.getAwsSecret()); + + // accessKey is still the same because it is derived from username + assertEquals(attempt3.getAwsAccessKey(), attempt2.getAwsAccessKey()); try { omClient.getS3Secret("HADOOP/JOHNDOE"); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java index ac7629a35d6d..d735a0ced650 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java @@ -177,7 +177,7 @@ public void testDoGet() throws Exception { om.getMetadataManager().getStore(), om.getMetrics().getDBCheckpointMetrics(), om.getAclsEnabled(), - om.getOzoneAdmins(om.getConfiguration()), + om.getOmAdminUsernames(), om.isSpnegoEnabled()); doNothing().when(responseMock).setContentType("application/x-tgz"); @@ -213,7 +213,7 @@ public void testSpnegoEnabled() throws Exception { setupCluster(); final OzoneManager om = cluster.getOzoneManager(); - Collection allowedUsers = om.getOzoneAdmins(om.getConfiguration()); + Collection allowedUsers = om.getOmAdminUsernames(); allowedUsers.add("recon"); doCallRealMethod().when(omDbCheckpointServletMock).initialize( diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index 7269c9a68ff8..a10bb67b7b52 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -93,6 +93,8 @@ enum Type { ListTrash = 91; RecoverTrash = 92; + + RevokeS3Secret = 93; } message OMRequest { @@ -165,6 +167,8 @@ message OMRequest { optional ListTrashRequest listTrashRequest = 91; optional RecoverTrashRequest RecoverTrashRequest = 92; + + optional RevokeS3SecretRequest RevokeS3SecretRequest = 93; } message OMResponse { @@ -1202,6 +1206,10 @@ message GetS3SecretResponse { required S3Secret s3Secret = 2; } +message RevokeS3SecretRequest { + required string kerberosID = 1; +} + /** This will be used internally by OM to replicate S3 Secret across quorum of OM's. diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java index c95132281d5e..6f66143c56f1 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java @@ -29,7 +29,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.util.Collection; /** @@ -62,25 +61,21 @@ public void init() throws ServletException { return; } - try { - OzoneConfiguration conf = om.getConfiguration(); - // Only Ozone Admins and Recon are allowed - Collection allowedUsers = om.getOzoneAdmins(conf); - ReconConfig reconConfig = conf.getObject(ReconConfig.class); - String reconPrincipal = reconConfig.getKerberosPrincipal(); - if (!reconPrincipal.isEmpty()) { - UserGroupInformation ugi = - UserGroupInformation.createRemoteUser(reconPrincipal); - allowedUsers.add(ugi.getShortUserName()); - } - - initialize(om.getMetadataManager().getStore(), - om.getMetrics().getDBCheckpointMetrics(), - om.getAclsEnabled(), - allowedUsers, - om.isSpnegoEnabled()); - } catch (IOException e) { - LOG.error("Error in getOzoneAdmins: {}", e.getMessage()); + OzoneConfiguration conf = om.getConfiguration(); + // Only Ozone Admins and Recon are allowed + Collection allowedUsers = om.getOmAdminUsernames(); + ReconConfig reconConfig = conf.getObject(ReconConfig.class); + String reconPrincipal = reconConfig.getKerberosPrincipal(); + if (!reconPrincipal.isEmpty()) { + UserGroupInformation ugi = + UserGroupInformation.createRemoteUser(reconPrincipal); + allowedUsers.add(ugi.getShortUserName()); } + + initialize(om.getMetadataManager().getStore(), + om.getMetrics().getDBCheckpointMetrics(), + om.getAclsEnabled(), + allowedUsers, + om.isSpnegoEnabled()); } } 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 d0f81c11c557..982d2297f2ac 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 @@ -162,6 +162,7 @@ import org.apache.hadoop.ozone.security.acl.RequestContext; import org.apache.hadoop.hdds.ExitManager; import org.apache.hadoop.ozone.util.OzoneVersionInfo; +import org.apache.hadoop.security.AccessControlException; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; @@ -201,6 +202,7 @@ import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED_DEFAULT; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS_WILDCARD; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_KEY_PREALLOCATION_BLOCKS_MAX; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_KEY_PREALLOCATION_BLOCKS_MAX_DEFAULT; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE; @@ -275,6 +277,11 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl private KeyManager keyManager; private PrefixManagerImpl prefixManager; + /** + * OM super user / admin list. + */ + private final Collection omAdminUsernames; + private final OMMetrics metrics; private final ProtocolMessageMetrics omClientProtocolMetrics; @@ -441,6 +448,9 @@ private OzoneManager(OzoneConfiguration conf) throws IOException, blockTokenMgr = createBlockTokenSecretManager(configuration); } + // Get admin list + omAdminUsernames = getOzoneAdminsFromConfig(configuration); + instantiateServices(); // Create special volume s3v which is required for S3G. @@ -548,7 +558,7 @@ private void instantiateServices() throws IOException { authorizer.setBucketManager(bucketManager); authorizer.setKeyManager(keyManager); authorizer.setPrefixManager(prefixManager); - authorizer.setOzoneAdmins(getOzoneAdmins(configuration)); + authorizer.setOzoneAdmins(omAdminUsernames); authorizer.setAllowListAllVolumes(allowListAllVolumes); } } else { @@ -2698,6 +2708,15 @@ public S3SecretValue getS3Secret(String kerberosID) throws IOException { return s3SecretManager.getS3Secret(kerberosID); } + @Override + /** + * {@inheritDoc} + */ + public void revokeS3Secret(String kerberosID) { + throw new UnsupportedOperationException("OzoneManager does not require " + + "this to be implemented. As write requests use a new approach"); + } + @Override public OmMultipartInfo initiateMultipartUpload(OmKeyArgs keyArgs) throws IOException { @@ -3531,9 +3550,9 @@ public OzoneDelegationTokenSecretManager getDelegationTokenMgr() { } /** - * Return list of OzoneAdministrators. + * Return list of OzoneAdministrators from config. */ - Collection getOzoneAdmins(OzoneConfiguration conf) + Collection getOzoneAdminsFromConfig(OzoneConfiguration conf) throws IOException { Collection ozAdmins = conf.getTrimmedStringCollection(OZONE_ADMINISTRATORS); @@ -3544,6 +3563,26 @@ Collection getOzoneAdmins(OzoneConfiguration conf) return ozAdmins; } + /** + * Return the list of Ozone administrators in effect. + */ + Collection getOmAdminUsernames() { + return omAdminUsernames; + } + + /** + * Return true if remoteUser is OM admin, false otherwise. + * @param remoteUser User name + * @throws AccessControlException + */ + public boolean isAdmin(String remoteUser) throws AccessControlException { + if (remoteUser == null || remoteUser.isEmpty()) { + return false; + } + return omAdminUsernames.contains(remoteUser) + || omAdminUsernames.contains(OZONE_ADMINISTRATORS_WILDCARD); + } + /** * Returns true if OzoneNativeAuthorizer is enabled and false if otherwise. * diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java index 14657c6576ce..29a0328eb38c 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java @@ -53,6 +53,7 @@ import org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadCommitPartRequest; import org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadCompleteRequest; import org.apache.hadoop.ozone.om.request.s3.security.S3GetSecretRequest; +import org.apache.hadoop.ozone.om.request.s3.security.S3RevokeSecretRequest; import org.apache.hadoop.ozone.om.request.security.OMCancelDelegationTokenRequest; import org.apache.hadoop.ozone.om.request.security.OMGetDelegationTokenRequest; import org.apache.hadoop.ozone.om.request.security.OMRenewDelegationTokenRequest; @@ -156,6 +157,8 @@ public static OMClientRequest createClientRequest(OMRequest omRequest) { return new S3GetSecretRequest(omRequest); case RecoverTrash: return new OMTrashRecoverRequest(omRequest); + case RevokeS3Secret: + return new S3RevokeSecretRequest(omRequest); default: throw new IllegalStateException("Unrecognized write command " + "type request" + cmdType); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3GetSecretRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3GetSecretRequest.java index b240373b937c..ef2eee4ce5b6 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3GetSecretRequest.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3GetSecretRequest.java @@ -73,10 +73,13 @@ public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { String kerberosID = s3GetSecretRequest.getKerberosID(); UserGroupInformation user = ProtobufRpcEngine.Server.getRemoteUser(); - if (!user.getUserName().equals(kerberosID)) { - throw new OMException("User mismatch. Requested user name is " + - "mismatched " + kerberosID +", with current user " + - user.getUserName(), OMException.ResultCodes.USER_MISMATCH); + // Permission check. Users need to be themselves or have admin privilege + if (!user.getUserName().equals(kerberosID) && + !ozoneManager.isAdmin(kerberosID)) { + throw new OMException("Requested user name '" + kerberosID + + "' doesn't match current user '" + user.getUserName() + + "', nor does current user has administrator privilege.", + OMException.ResultCodes.USER_MISMATCH); } String s3Secret = DigestUtils.sha256Hex(OmUtils.getSHADigest()); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3RevokeSecretRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3RevokeSecretRequest.java new file mode 100644 index 000000000000..6ee2007e4da8 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3RevokeSecretRequest.java @@ -0,0 +1,155 @@ +/** + * 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.om.request.s3.security; + +import com.google.common.base.Optional; +import org.apache.hadoop.hdds.utils.db.cache.CacheKey; +import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ipc.ProtobufRpcEngine; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.audit.OMAction; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper; +import org.apache.hadoop.ozone.om.request.OMClientRequest; +import org.apache.hadoop.ozone.om.request.util.OmResponseUtil; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.om.response.s3.security.S3RevokeSecretResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RevokeS3SecretRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status; +import org.apache.hadoop.security.UserGroupInformation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.S3_SECRET_LOCK; + +/** + * Handles RevokeS3Secret request. + */ +public class S3RevokeSecretRequest extends OMClientRequest { + + private static final Logger LOG = + LoggerFactory.getLogger(S3RevokeSecretRequest.class); + + public S3RevokeSecretRequest(OMRequest omRequest) { + super(omRequest); + } + + @Override + public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { + final RevokeS3SecretRequest s3RevokeSecretRequest = + getOmRequest().getRevokeS3SecretRequest(); + final String kerberosID = s3RevokeSecretRequest.getKerberosID(); + final UserGroupInformation user = ProtobufRpcEngine.Server.getRemoteUser(); + + // Permission check. Users need to be themselves or have admin privilege + if (!user.getUserName().equals(kerberosID) && + !ozoneManager.isAdmin(kerberosID)) { + throw new OMException("Requested user name '" + kerberosID + + "' doesn't match current user '" + user.getUserName() + + "', nor does current user has administrator privilege.", + OMException.ResultCodes.USER_MISMATCH); + } + + final RevokeS3SecretRequest revokeS3SecretRequest = + RevokeS3SecretRequest.newBuilder() + .setKerberosID(kerberosID).build(); + + OMRequest.Builder omRequest = OMRequest.newBuilder() + .setRevokeS3SecretRequest(revokeS3SecretRequest) + .setCmdType(getOmRequest().getCmdType()) + .setClientId(getOmRequest().getClientId()); + + if (getOmRequest().hasTraceID()) { + omRequest.setTraceID(getOmRequest().getTraceID()); + } + + return omRequest.build(); + } + + @Override + public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, + long transactionLogIndex, + OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) { + + OMClientResponse omClientResponse = null; + OMResponse.Builder omResponse = + OmResponseUtil.getOMResponseBuilder(getOmRequest()); + boolean acquiredLock = false; + IOException exception = null; + OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); + + final RevokeS3SecretRequest revokeS3SecretRequest = + getOmRequest().getRevokeS3SecretRequest(); + String kerberosID = revokeS3SecretRequest.getKerberosID(); + try { + acquiredLock = + omMetadataManager.getLock().acquireWriteLock(S3_SECRET_LOCK, + kerberosID); + + // Remove if entry exists in table + if (omMetadataManager.getS3SecretTable().isExist(kerberosID)) { + // Invalid entry in table cache immediately + omMetadataManager.getKeyTable().addCacheEntry( + new CacheKey<>(kerberosID), + new CacheValue<>(Optional.absent(), transactionLogIndex)); + omClientResponse = new S3RevokeSecretResponse(kerberosID, + omResponse.setStatus(Status.OK).build()); + } else { + omClientResponse = new S3RevokeSecretResponse(null, + omResponse.setStatus(Status.S3_SECRET_NOT_FOUND).build()); + } + } catch (IOException ex) { + exception = ex; + omClientResponse = new S3RevokeSecretResponse(null, + createErrorOMResponse(omResponse, ex)); + } finally { + addResponseToDoubleBuffer(transactionLogIndex, omClientResponse, + ozoneManagerDoubleBufferHelper); + if (acquiredLock) { + omMetadataManager.getLock().releaseWriteLock(S3_SECRET_LOCK, + kerberosID); + } + } + + Map auditMap = new HashMap<>(); + auditMap.put(OzoneConsts.S3_REVOKESECRET_USER, kerberosID); + auditLog(ozoneManager.getAuditLogger(), buildAuditMessage( + OMAction.REVOKE_S3_SECRET, auditMap, + exception, getOmRequest().getUserInfo())); + + if (exception == null) { + if (omResponse.getStatus() == Status.OK) { + LOG.info("Secret for {} is revoked.", kerberosID); + } else { + LOG.info("Secret for {} doesn't exist.", kerberosID); + } + } else { + LOG.error("Error when revoking secret for {}.", kerberosID, exception); + } + return omClientResponse; + } +} diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/security/S3RevokeSecretResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/security/S3RevokeSecretResponse.java new file mode 100644 index 000000000000..05633010eb74 --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/security/S3RevokeSecretResponse.java @@ -0,0 +1,57 @@ +/** + * 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.om.response.s3.security; + +import org.apache.hadoop.hdds.utils.db.BatchOperation; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.response.CleanupTableInfo; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; + +import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.S3_SECRET_TABLE; + +/** + * Response for RevokeS3Secret request. + */ +@CleanupTableInfo(cleanupTables = {S3_SECRET_TABLE}) +public class S3RevokeSecretResponse extends OMClientResponse { + + private final String kerberosID; + + public S3RevokeSecretResponse(@Nullable String kerberosID, + @Nonnull OMResponse omResponse) { + super(omResponse); + this.kerberosID = kerberosID; + } + + @Override + public void addToDBBatch(OMMetadataManager omMetadataManager, + BatchOperation batchOperation) throws IOException { + + if (kerberosID != null && getOMResponse().getStatus() == Status.OK) { + omMetadataManager.getS3SecretTable().deleteWithBatch(batchOperation, + kerberosID); + } + } +} diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/GetS3SecretHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/GetS3SecretHandler.java index b6506a54e747..df58eea80b38 100644 --- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/GetS3SecretHandler.java +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/GetS3SecretHandler.java @@ -34,6 +34,11 @@ description = "Returns s3 secret for current user") public class GetS3SecretHandler extends S3Handler { + @Option(names = "-u", + description = "Specify the user name to perform the operation on " + + "(admins only)'") + private String username; + @Option(names = "-e", description = "Print out variables together with 'export' prefix, to " + "use it from 'eval $(ozone s3 getsecret)'") @@ -47,9 +52,11 @@ protected boolean isApplicable() { @Override protected void execute(OzoneClient client, OzoneAddress address) throws IOException { - String userName = UserGroupInformation.getCurrentUser().getUserName(); + if (username == null || username.isEmpty()) { + username = UserGroupInformation.getCurrentUser().getUserName(); + } - final S3SecretValue secret = client.getObjectStore().getS3Secret(userName); + final S3SecretValue secret = client.getObjectStore().getS3Secret(username); if (export) { out().println("export AWS_ACCESS_KEY_ID=" + secret.getAwsAccessKey()); out().println("export AWS_SECRET_ACCESS_KEY=" + secret.getAwsSecret()); diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/RevokeS3SecretHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/RevokeS3SecretHandler.java new file mode 100644 index 000000000000..be35bb791960 --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/RevokeS3SecretHandler.java @@ -0,0 +1,76 @@ +/* + * 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.shell.s3; + +import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.shell.OzoneAddress; +import org.apache.hadoop.security.UserGroupInformation; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +/** + * Executes revokesecret calls. + */ +@Command(name = "revokesecret", + description = "Revoke s3 secret for current user") +public class RevokeS3SecretHandler extends S3Handler { + + @Option(names = "-u", + description = "Specify the user name to perform the operation on " + + "(admins only)'") + private String username; + + @Option(names = "-y", + description = "Continue without interactive user confirmation") + private boolean yes; + + @Override + protected boolean isApplicable() { + return securityEnabled(); + } + + @Override + protected void execute(OzoneClient client, OzoneAddress address) + throws IOException { + if (username == null || username.isEmpty()) { + username = UserGroupInformation.getCurrentUser().getUserName(); + } + + if (!yes) { + // Ask for user confirmation + out().print("Enter 'y' to confirm S3 secret revocation for '" + + username + "': "); + out().flush(); + Scanner scanner = new Scanner(new InputStreamReader( + System.in, StandardCharsets.UTF_8)); + String confirmation = scanner.next().trim().toLowerCase(); + if (!confirmation.equals("y")) { + out().println("Operation cancelled."); + return; + } + } + + client.getObjectStore().revokeS3Secret(username); + out().println("S3 secret revoked."); + } +} diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java index 41163723a5b6..d85b0e9b7b6e 100644 --- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java @@ -30,7 +30,8 @@ @Command(name = "ozone s3", description = "Shell for S3 specific operations", subcommands = { - GetS3SecretHandler.class + GetS3SecretHandler.class, + RevokeS3SecretHandler.class }) public class S3Shell extends Shell {