Skip to content

Commit

Permalink
HDDS-11041. Add admin request filter for S3 requests and UGI support …
Browse files Browse the repository at this point in the history
…for GrpcOmTransport (apache#7268)
  • Loading branch information
devabhishekpal authored Oct 26, 2024
1 parent 0b84998 commit 24c1000
Show file tree
Hide file tree
Showing 18 changed files with 376 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
*/
package org.apache.hadoop.hdds.server;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import com.google.common.collect.Sets;

import jakarta.annotation.Nullable;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
Expand All @@ -33,6 +36,8 @@
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS_WILDCARD;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS_GROUPS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_S3_ADMINISTRATORS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_S3_ADMINISTRATORS_GROUPS;

/**
* This class contains ozone admin user information, username and group,
Expand Down Expand Up @@ -186,4 +191,88 @@ public static Collection<String> getOzoneReadOnlyAdminsGroupsFromConfig(
return conf.getTrimmedStringCollection(
OZONE_READONLY_ADMINISTRATORS_GROUPS);
}

/**
* Get the list of S3 administrators from Ozone config.
* <p/>
* <strong>Notes</strong>:
* <ul>
* <li>If <code>ozone.s3.administrators</code> value is empty string or unset,
* defaults to <code>ozone.administrators</code> value.</li>
* <li>If current user is not part of the administrators group,
* {@link UserGroupInformation#getCurrentUser()} will be added to the resulting list</li>
* </ul>
* @param conf An instance of {@link OzoneConfiguration} being used
* @return A {@link Collection} of the S3 administrator users
*/
public static Set<String> getS3AdminsFromConfig(OzoneConfiguration conf) throws IOException {
Set<String> ozoneAdmins = new HashSet<>(conf.getTrimmedStringCollection(OZONE_S3_ADMINISTRATORS));

if (ozoneAdmins.isEmpty()) {
ozoneAdmins = new HashSet<>(conf.getTrimmedStringCollection(OZONE_ADMINISTRATORS));
}

String omSPN = UserGroupInformation.getCurrentUser().getShortUserName();
ozoneAdmins.add(omSPN);

return ozoneAdmins;
}

/**
* Get the list of the groups that are a part of S3 administrators from Ozone config.
* <p/>
* <strong>Note</strong>: If <code>ozone.s3.administrators.groups</code> value is empty or unset,
* defaults to the <code>ozone.administrators.groups</code> value
*
* @param conf An instance of {@link OzoneConfiguration} being used
* @return A {@link Collection} of the S3 administrator groups
*/
public static Set<String> getS3AdminsGroupsFromConfig(OzoneConfiguration conf) {
Set<String> s3AdminsGroup = new HashSet<>(conf.getTrimmedStringCollection(OZONE_S3_ADMINISTRATORS_GROUPS));

if (s3AdminsGroup.isEmpty() && conf.getTrimmedStringCollection(OZONE_S3_ADMINISTRATORS).isEmpty()) {
s3AdminsGroup = new HashSet<>(conf.getTrimmedStringCollection(OZONE_ADMINISTRATORS_GROUPS));
}

return s3AdminsGroup;
}

/**
* Get the users and groups that are a part of S3 administrators.
* @param conf Stores an instance of {@link OzoneConfiguration} being used
* @return an instance of {@link OzoneAdmins} containing the S3 admin users and groups
*/
public static OzoneAdmins getS3Admins(OzoneConfiguration conf) {
Set<String> s3Admins;
try {
s3Admins = getS3AdminsFromConfig(conf);
} catch (IOException ie) {
s3Admins = Collections.emptySet();
}
Set<String> s3AdminGroups = getS3AdminsGroupsFromConfig(conf);

return new OzoneAdmins(s3Admins, s3AdminGroups);
}

/**
* Check if the provided user is an S3 administrator.
* @param user An instance of {@link UserGroupInformation} with information about the user to verify
* @param s3Admins An instance of {@link OzoneAdmins} containing information
* of the S3 administrator users and groups in the system
* @return {@code true} if the provided user is an S3 administrator else {@code false}
*/
public static boolean isS3Admin(@Nullable UserGroupInformation user, OzoneAdmins s3Admins) {
return null != user && s3Admins.isAdmin(user);
}

/**
* Check if the provided user is an S3 administrator.
* @param user An instance of {@link UserGroupInformation} with information about the user to verify
* @param conf An instance of {@link OzoneConfiguration} being used
* @return {@code true} if the provided user is an S3 administrator else {@code false}
*/
public static boolean isS3Admin(@Nullable UserGroupInformation user, OzoneConfiguration conf) {
OzoneAdmins s3Admins = getS3Admins(conf);
return isS3Admin(user, s3Admins);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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.hdds.server;

import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.ozone.OzoneConfigKeys;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;
import java.util.Arrays;

import static org.assertj.core.api.Assertions.assertThat;

/**
* This class is to test the utilities present in the OzoneAdmins class.
*/
class TestOzoneAdmins {
// The following set of tests are to validate the S3 based utilities present in OzoneAdmins
private OzoneConfiguration configuration;

@BeforeEach
void setUp() {
configuration = new OzoneConfiguration();
}

@ParameterizedTest
@ValueSource(strings = {OzoneConfigKeys.OZONE_S3_ADMINISTRATORS,
OzoneConfigKeys.OZONE_ADMINISTRATORS})
void testS3AdminExtraction(String configKey) throws IOException {
configuration.set(configKey, "alice,bob");

assertThat(OzoneAdmins.getS3AdminsFromConfig(configuration))
.containsAll(Arrays.asList("alice", "bob"));
}

@ParameterizedTest
@ValueSource(strings = {OzoneConfigKeys.OZONE_S3_ADMINISTRATORS_GROUPS,
OzoneConfigKeys.OZONE_ADMINISTRATORS_GROUPS})
void testS3AdminGroupExtraction(String configKey) {
configuration.set(configKey, "test1, test2");

assertThat(OzoneAdmins.getS3AdminsGroupsFromConfig(configuration))
.containsAll(Arrays.asList("test1", "test2"));
}

@ParameterizedTest
@CsvSource({
OzoneConfigKeys.OZONE_ADMINISTRATORS + ", " + OzoneConfigKeys.OZONE_ADMINISTRATORS_GROUPS,
OzoneConfigKeys.OZONE_S3_ADMINISTRATORS + ", " + OzoneConfigKeys.OZONE_S3_ADMINISTRATORS_GROUPS
})
void testIsAdmin(String adminKey, String adminGroupKey) {
// When there is no S3 admin, but Ozone admins present
configuration.set(adminKey, "alice");
configuration.set(adminGroupKey, "test_group");

OzoneAdmins admins = OzoneAdmins.getS3Admins(configuration);
UserGroupInformation ugi = UserGroupInformation.createUserForTesting(
"alice", new String[] {"test_group"});

assertThat(admins.isAdmin(ugi)).isEqualTo(true);

// Test that when a user is present in an admin group but not an Ozone Admin
UserGroupInformation ugiGroupOnly = UserGroupInformation.createUserForTesting(
"bob", new String[] {"test_group"});
assertThat(admins.isAdmin(ugiGroupOnly)).isEqualTo(true);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void testIsAdminWithUgi(boolean isAdminSet) {
if (isAdminSet) {
configuration.set(OzoneConfigKeys.OZONE_ADMINISTRATORS, "alice");
configuration.set(OzoneConfigKeys.OZONE_ADMINISTRATORS_GROUPS, "test_group");
}
OzoneAdmins admins = OzoneAdmins.getS3Admins(configuration);
UserGroupInformation ugi = UserGroupInformation.createUserForTesting(
"alice", new String[] {"test_group"});
// Test that when a user is present in an admin group but not an Ozone Admin
UserGroupInformation ugiGroupOnly = UserGroupInformation.createUserForTesting(
"bob", new String[] {"test_group"});

assertThat(admins.isAdmin(ugi)).isEqualTo(isAdminSet);
assertThat(admins.isAdmin(ugiGroupOnly)).isEqualTo(isAdminSet);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void testIsS3AdminWithUgiAndConfiguration(boolean isAdminSet) {
if (isAdminSet) {
configuration.set(OzoneConfigKeys.OZONE_S3_ADMINISTRATORS, "alice");
configuration.set(OzoneConfigKeys.OZONE_S3_ADMINISTRATORS_GROUPS, "test_group");
UserGroupInformation ugi = UserGroupInformation.createUserForTesting(
"alice", new String[] {"test_group"});
// Scenario when user is present in an admin group but not an Ozone Admin
UserGroupInformation ugiGroupOnly = UserGroupInformation.createUserForTesting(
"bob", new String[] {"test_group"});

assertThat(OzoneAdmins.isS3Admin(ugi, configuration)).isEqualTo(true);
assertThat(OzoneAdmins.isS3Admin(ugiGroupOnly, configuration)).isEqualTo(true);
} else {
assertThat(OzoneAdmins.isS3Admin(null, configuration)).isEqualTo(false);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@
package org.apache.hadoop.ozone.om.ha;

import io.grpc.Status;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdds.conf.ConfigurationException;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.HddsUtils;
import org.apache.hadoop.hdds.utils.LegacyHadoopConfigurationSource;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.ozone.OmUtils;
import org.apache.hadoop.ozone.OzoneConsts;
Expand All @@ -41,6 +38,7 @@
import java.util.Optional;
import java.util.OptionalInt;
import io.grpc.StatusRuntimeException;
import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -60,9 +58,10 @@ public class GrpcOMFailoverProxyProvider<T> extends
LoggerFactory.getLogger(GrpcOMFailoverProxyProvider.class);

public GrpcOMFailoverProxyProvider(ConfigurationSource configuration,
UserGroupInformation ugi,
String omServiceId,
Class<T> protocol) throws IOException {
super(configuration, omServiceId, protocol);
super(configuration, ugi, omServiceId, protocol);
}

@Override
Expand Down Expand Up @@ -116,9 +115,7 @@ protected void loadOMClientConfigs(ConfigurationSource config, String omSvcId)

private T createOMProxy() throws IOException {
InetSocketAddress addr = new InetSocketAddress(0);
Configuration hadoopConf =
LegacyHadoopConfigurationSource.asHadoopConfiguration(getConf());
return (T) RPC.getProxy(getInterface(), 0, addr, hadoopConf);
return createOMProxy(addr);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,9 @@
import java.util.List;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.utils.LegacyHadoopConfigurationSource;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.retry.RetryPolicies;
import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.ozone.OmUtils;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.ha.ConfUtils;
Expand All @@ -59,9 +53,7 @@ public class HadoopRpcOMFailoverProxyProvider<T> extends
public static final Logger LOG =
LoggerFactory.getLogger(HadoopRpcOMFailoverProxyProvider.class);

private final long omVersion;
private final Text delegationTokenService;
private final UserGroupInformation ugi;
private Map<String, OMProxyInfo> omProxyInfos;
private List<String> retryExceptions = new ArrayList<>();

Expand All @@ -75,9 +67,7 @@ public HadoopRpcOMFailoverProxyProvider(ConfigurationSource configuration,
UserGroupInformation ugi,
String omServiceId,
Class<T> protocol) throws IOException {
super(configuration, omServiceId, protocol);
this.ugi = ugi;
this.omVersion = RPC.getProtocolVersion(protocol);
super(configuration, ugi, omServiceId, protocol);
this.delegationTokenService = computeDelegationTokenService();
}

Expand Down Expand Up @@ -130,24 +120,6 @@ protected void loadOMClientConfigs(ConfigurationSource config, String omSvcId)
setOmNodeAddressMap(omNodeAddressMap);
}

private T createOMProxy(InetSocketAddress omAddress) throws IOException {
Configuration hadoopConf =
LegacyHadoopConfigurationSource.asHadoopConfiguration(getConf());
RPC.setProtocolEngine(hadoopConf, getInterface(), ProtobufRpcEngine.class);

// FailoverOnNetworkException ensures that the IPC layer does not attempt
// retries on the same OM in case of connection exception. This retry
// policy essentially results in TRY_ONCE_THEN_FAIL.
RetryPolicy connectionRetryPolicy = RetryPolicies
.failoverOnNetworkException(0);

return (T) RPC.getProtocolProxy(getInterface(), omVersion,
omAddress, ugi, hadoopConf, NetUtils.getDefaultSocketFactory(
hadoopConf), (int) OmUtils.getOMClientRpcTimeOut(getConf()),
connectionRetryPolicy).getProxy();

}

/**
* Get the proxy object which should be used until the next failover event
* occurs. RPC proxy object is intialized lazily.
Expand Down
Loading

0 comments on commit 24c1000

Please sign in to comment.