001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.access;
019
020import static org.junit.Assert.assertFalse;
021import static org.junit.Assert.assertTrue;
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.concurrent.atomic.AtomicBoolean;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.Abortable;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.Waiter.Predicate;
032import org.apache.hadoop.hbase.security.User;
033import org.apache.hadoop.hbase.testclassification.MediumTests;
034import org.apache.hadoop.hbase.testclassification.SecurityTests;
035import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
036import org.junit.AfterClass;
037import org.junit.BeforeClass;
038import org.junit.ClassRule;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
045import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
046
047/**
048 * Test the reading and writing of access permissions to and from zookeeper.
049 */
050@Category({ SecurityTests.class, MediumTests.class })
051public class TestZKPermissionWatcher {
052
053  @ClassRule
054  public static final HBaseClassTestRule CLASS_RULE =
055    HBaseClassTestRule.forClass(TestZKPermissionWatcher.class);
056
057  private static final Logger LOG = LoggerFactory.getLogger(TestZKPermissionWatcher.class);
058  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
059  private static AuthManager AUTH_A;
060  private static AuthManager AUTH_B;
061  private static ZKPermissionWatcher WATCHER_A;
062  private static ZKPermissionWatcher WATCHER_B;
063  private final static Abortable ABORTABLE = new Abortable() {
064    private final AtomicBoolean abort = new AtomicBoolean(false);
065
066    @Override
067    public void abort(String why, Throwable e) {
068      LOG.info(why, e);
069      abort.set(true);
070    }
071
072    @Override
073    public boolean isAborted() {
074      return abort.get();
075    }
076  };
077
078  private static TableName TEST_TABLE = TableName.valueOf("perms_test");
079
080  @BeforeClass
081  public static void beforeClass() throws Exception {
082    // setup configuration
083    Configuration conf = UTIL.getConfiguration();
084    SecureTestUtil.enableSecurity(conf);
085
086    // start minicluster
087    UTIL.startMiniCluster();
088    AUTH_A = new AuthManager(conf);
089    AUTH_B = new AuthManager(conf);
090    WATCHER_A = new ZKPermissionWatcher(
091      new ZKWatcher(conf, "TestZKPermissionsWatcher_1", ABORTABLE), AUTH_A, conf);
092    WATCHER_B = new ZKPermissionWatcher(
093      new ZKWatcher(conf, "TestZKPermissionsWatcher_2", ABORTABLE), AUTH_B, conf);
094    WATCHER_A.start();
095    WATCHER_B.start();
096  }
097
098  @AfterClass
099  public static void afterClass() throws Exception {
100    WATCHER_A.close();
101    WATCHER_B.close();
102    UTIL.shutdownMiniCluster();
103  }
104
105  @Test
106  public void testPermissionsWatcher() throws Exception {
107    Configuration conf = UTIL.getConfiguration();
108    User george = User.createUserForTesting(conf, "george", new String[] {});
109    User hubert = User.createUserForTesting(conf, "hubert", new String[] {});
110    ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create();
111
112    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
113    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
114    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
115    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
116
117    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
118    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
119    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
120    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
121
122    // update ACL: george RW
123    writeToZookeeper(WATCHER_A,
124      updatePermissions(permissions, george, Permission.Action.READ, Permission.Action.WRITE));
125    waitForModification(AUTH_B, 1000);
126
127    // check it
128    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
129    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
130    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
131    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
132    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
133    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
134    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
135    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
136
137    // update ACL: hubert R
138    writeToZookeeper(WATCHER_B, updatePermissions(permissions, hubert, Permission.Action.READ));
139    waitForModification(AUTH_A, 1000);
140
141    // check it
142    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
143    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
144    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
145    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
146    assertTrue(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
147    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
148    assertTrue(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
149    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
150  }
151
152  @Test
153  public void testRaceConditionOnPermissionUpdate() throws Exception {
154    Configuration conf = UTIL.getConfiguration();
155    User george = User.createUserForTesting(conf, "george", new String[] {});
156    User hubert = User.createUserForTesting(conf, "hubert", new String[] {});
157    ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create();
158
159    // update ACL: george RW
160    writeToZookeeper(WATCHER_A,
161      updatePermissions(permissions, george, Permission.Action.READ, Permission.Action.WRITE));
162    waitForModification(AUTH_A, 1000);
163
164    // check it
165    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
166    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
167
168    // update ACL: hubert A
169    writeToZookeeper(WATCHER_A, updatePermissions(permissions, hubert, Permission.Action.ADMIN));
170    // intended not to waitForModification(AUTH_A, 1000);
171    // check george permission should not be updated/removed while updating permission for hubert
172    for (int i = 0; i < 5000; i++) {
173      assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
174      assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
175    }
176  }
177
178  private ListMultimap<String, UserPermission> updatePermissions(
179    ListMultimap<String, UserPermission> permissions, User user, Permission.Action... actions) {
180    List<UserPermission> acl = new ArrayList<>(1);
181    acl.add(new UserPermission(user.getShortName(),
182      Permission.newBuilder(TEST_TABLE).withActions(actions).build()));
183    permissions.putAll(user.getShortName(), acl);
184    return permissions;
185  }
186
187  private void writeToZookeeper(ZKPermissionWatcher watcher,
188    ListMultimap<String, UserPermission> permissions) {
189    byte[] serialized =
190      PermissionStorage.writePermissionsAsBytes(permissions, UTIL.getConfiguration());
191    watcher.writeToZookeeper(TEST_TABLE.getName(), serialized);
192  }
193
194  private void waitForModification(AuthManager authManager, long sleep) throws Exception {
195    final long mtime = authManager.getMTime();
196    // Wait for the update to propagate
197    UTIL.waitFor(10000, 100, new Predicate<Exception>() {
198      @Override
199      public boolean evaluate() throws Exception {
200        return authManager.getMTime() > mtime;
201      }
202    });
203    Thread.sleep(sleep);
204  }
205}