From 5c1acf4792a7f7b6a5ace11c2fa4d172ede46b4e Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 22 Nov 2017 11:14:40 -0500 Subject: [PATCH] HBASE-19318 Use the PB service interface as the judge of whether some security feature exists Hard-coded checks on HBase implementations (e.g. AccessController and VisibilityController) preclude custom implementations of authorization and visibility labels (Apache Ranger). Signed-off-by: Ted Yu --- .../hbase/master/MasterRpcServices.java | 50 +++++- .../master/TestMasterCoprocessorServices.java | 170 ++++++++++++++++++ 2 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterCoprocessorServices.java diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java index 1a8fd064997..ce85b66cb47 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java @@ -47,6 +47,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.client.TableState; import org.apache.hadoop.hbase.client.VersionInfoUtil; import org.apache.hadoop.hbase.client.replication.ReplicationPeerConfigUtil; +import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; import org.apache.hadoop.hbase.errorhandling.ForeignException; import org.apache.hadoop.hbase.exceptions.UnknownProtocolException; import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils; @@ -64,6 +65,8 @@ import org.apache.hadoop.hbase.procedure2.LockType; import org.apache.hadoop.hbase.procedure2.LockedResource; import org.apache.hadoop.hbase.procedure2.Procedure; import org.apache.hadoop.hbase.procedure2.ProcedureUtil; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsService; import org.apache.hadoop.hbase.quotas.MasterQuotaManager; import org.apache.hadoop.hbase.quotas.QuotaObserverChore; import org.apache.hadoop.hbase.quotas.QuotaUtil; @@ -1768,9 +1771,9 @@ public class MasterRpcServices extends RSRpcServices } else { capabilities.add(SecurityCapabilitiesResponse.Capability.SIMPLE_AUTHENTICATION); } - // The AccessController can provide AUTHORIZATION and CELL_AUTHORIZATION - if (master.cpHost != null && - master.cpHost.findCoprocessor(AccessController.class.getName()) != null) { + // A coprocessor that implements AccessControlService can provide AUTHORIZATION and + // CELL_AUTHORIZATION + if (master.cpHost != null && hasAccessControlServiceCoprocessor(master.cpHost)) { if (AccessController.isAuthorizationSupported(master.getConfiguration())) { capabilities.add(SecurityCapabilitiesResponse.Capability.AUTHORIZATION); } @@ -1778,9 +1781,8 @@ public class MasterRpcServices extends RSRpcServices capabilities.add(SecurityCapabilitiesResponse.Capability.CELL_AUTHORIZATION); } } - // The VisibilityController can provide CELL_VISIBILITY - if (master.cpHost != null && - master.cpHost.findCoprocessor(VisibilityController.class.getName()) != null) { + // A coprocessor that implements VisibilityLabelsService can provide CELL_VISIBILITY. + if (master.cpHost != null && hasVisibilityLabelsServiceCoprocessor(master.cpHost)) { if (VisibilityController.isCellAuthorizationSupported(master.getConfiguration())) { capabilities.add(SecurityCapabilitiesResponse.Capability.CELL_VISIBILITY); } @@ -1792,6 +1794,42 @@ public class MasterRpcServices extends RSRpcServices return response.build(); } + /** + * Determines if there is a MasterCoprocessor deployed which implements + * {@link org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.Interface}. + */ + boolean hasAccessControlServiceCoprocessor(MasterCoprocessorHost cpHost) { + return checkCoprocessorWithService( + cpHost.findCoprocessors(MasterCoprocessor.class), AccessControlService.Interface.class); + } + + /** + * Determines if there is a MasterCoprocessor deployed which implements + * {@link org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsService.Interface}. + */ + boolean hasVisibilityLabelsServiceCoprocessor(MasterCoprocessorHost cpHost) { + return checkCoprocessorWithService( + cpHost.findCoprocessors(MasterCoprocessor.class), + VisibilityLabelsService.Interface.class); + } + + /** + * Determines if there is a coprocessor implementation in the provided argument which extends + * or implements the provided {@code service}. + */ + boolean checkCoprocessorWithService( + List coprocessorsToCheck, Class service) { + if (coprocessorsToCheck == null || coprocessorsToCheck.isEmpty()) { + return false; + } + for (MasterCoprocessor cp : coprocessorsToCheck) { + if (service.isAssignableFrom(cp.getClass())) { + return true; + } + } + return false; + } + private MasterSwitchType convert(MasterProtos.MasterSwitchType switchType) { switch (switchType) { case SPLIT: diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterCoprocessorServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterCoprocessorServices.java new file mode 100644 index 00000000000..af550ab2d0c --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterCoprocessorServices.java @@ -0,0 +1,170 @@ +/* + * 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.hbase.master; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.hadoop.hbase.JMXListener; +import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; +import org.apache.hadoop.hbase.coprocessor.MasterObserver; +import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; +import org.apache.hadoop.hbase.coprocessor.RegionObserver; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsRequest; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsResponse; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GetUserPermissionsRequest; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GetUserPermissionsResponse; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantRequest; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantResponse; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeRequest; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeResponse; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsRequest; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.ListLabelsRequest; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.ListLabelsResponse; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.SetAuthsRequest; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsRequest; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsService; +import org.apache.hadoop.hbase.security.access.AccessController; +import org.apache.hadoop.hbase.security.visibility.VisibilityController; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.google.protobuf.RpcCallback; +import com.google.protobuf.RpcController; + +/** + * Tests that the MasterRpcServices is correctly searching for implementations of the + * Coprocessor Service and not just the "default" implementations of those services. + */ +@Category({SmallTests.class}) +public class TestMasterCoprocessorServices { + private static class MockAccessController implements AccessControlService.Interface, + MasterCoprocessor, RegionCoprocessor, MasterObserver, RegionObserver { + + @Override + public void grant(RpcController controller, GrantRequest request, + RpcCallback done) {} + + @Override + public void revoke(RpcController controller, RevokeRequest request, + RpcCallback done) {} + + @Override + public void getUserPermissions(RpcController controller, GetUserPermissionsRequest request, + RpcCallback done) {} + + @Override + public void checkPermissions(RpcController controller, CheckPermissionsRequest request, + RpcCallback done) {} + } + + private static class MockVisibilityController implements VisibilityLabelsService.Interface, + MasterCoprocessor, RegionCoprocessor, MasterObserver, RegionObserver { + + @Override + public void addLabels(RpcController controller, VisibilityLabelsRequest request, + RpcCallback done) { + } + + @Override + public void setAuths(RpcController controller, SetAuthsRequest request, + RpcCallback done) { + } + + @Override + public void clearAuths(RpcController controller, SetAuthsRequest request, + RpcCallback done) { + } + + @Override + public void getAuths(RpcController controller, GetAuthsRequest request, + RpcCallback done) { + } + + @Override + public void listLabels(RpcController controller, ListLabelsRequest request, + RpcCallback done) { + } + } + + private MasterRpcServices masterServices; + + @SuppressWarnings("unchecked") + @Before + public void setup() { + masterServices = mock(MasterRpcServices.class); + when(masterServices.hasAccessControlServiceCoprocessor( + any(MasterCoprocessorHost.class))).thenCallRealMethod(); + when(masterServices.hasVisibilityLabelsServiceCoprocessor( + any(MasterCoprocessorHost.class))).thenCallRealMethod(); + when(masterServices.checkCoprocessorWithService( + any(List.class), any(Class.class))).thenCallRealMethod(); + } + + @Test + public void testAccessControlServices() { + MasterCoprocessor defaultImpl = new AccessController(); + MasterCoprocessor customImpl = new MockAccessController(); + MasterCoprocessor unrelatedImpl = new JMXListener(); + assertTrue(masterServices.checkCoprocessorWithService( + Collections.singletonList(defaultImpl), AccessControlService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Collections.singletonList(customImpl), AccessControlService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + Collections.emptyList(), AccessControlService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + null, AccessControlService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + Collections.singletonList(unrelatedImpl), AccessControlService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Arrays.asList(unrelatedImpl, customImpl), AccessControlService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Arrays.asList(unrelatedImpl, defaultImpl), AccessControlService.Interface.class)); + } + + @Test + public void testVisibilityLabelServices() { + MasterCoprocessor defaultImpl = new VisibilityController(); + MasterCoprocessor customImpl = new MockVisibilityController(); + MasterCoprocessor unrelatedImpl = new JMXListener(); + assertTrue(masterServices.checkCoprocessorWithService( + Collections.singletonList(defaultImpl), VisibilityLabelsService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Collections.singletonList(customImpl), VisibilityLabelsService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + Collections.emptyList(), VisibilityLabelsService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + null, VisibilityLabelsService.Interface.class)); + assertFalse(masterServices.checkCoprocessorWithService( + Collections.singletonList(unrelatedImpl), VisibilityLabelsService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Arrays.asList(unrelatedImpl, customImpl), VisibilityLabelsService.Interface.class)); + assertTrue(masterServices.checkCoprocessorWithService( + Arrays.asList(unrelatedImpl, defaultImpl), VisibilityLabelsService.Interface.class)); + } +}