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
111    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
112    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
113    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
114    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
115
116    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
117    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
118    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
119    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
120
121    // update ACL: george RW
122    List<UserPermission> acl = new ArrayList<>(1);
123    acl.add(new UserPermission(george.getShortName(), Permission.newBuilder(TEST_TABLE)
124      .withActions(Permission.Action.READ, Permission.Action.WRITE).build()));
125    ListMultimap<String, UserPermission> multimap = ArrayListMultimap.create();
126    multimap.putAll(george.getShortName(), acl);
127    byte[] serialized = PermissionStorage.writePermissionsAsBytes(multimap, conf);
128    WATCHER_A.writeToZookeeper(TEST_TABLE.getName(), serialized);
129    final long mtimeB = AUTH_B.getMTime();
130    // Wait for the update to propagate
131    UTIL.waitFor(10000, 100, new Predicate<Exception>() {
132      @Override
133      public boolean evaluate() throws Exception {
134        return AUTH_B.getMTime() > mtimeB;
135      }
136    });
137    Thread.sleep(1000);
138
139    // check it
140    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
141    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
142    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
143    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
144    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
145    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
146    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
147    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
148
149    // update ACL: hubert R
150    List<UserPermission> acl2 = new ArrayList<>(1);
151    acl2.add(new UserPermission(hubert.getShortName(),
152      Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build()));
153    final long mtimeA = AUTH_A.getMTime();
154    multimap.putAll(hubert.getShortName(), acl2);
155    byte[] serialized2 = PermissionStorage.writePermissionsAsBytes(multimap, conf);
156    WATCHER_B.writeToZookeeper(TEST_TABLE.getName(), serialized2);
157    // Wait for the update to propagate
158    UTIL.waitFor(10000, 100, new Predicate<Exception>() {
159      @Override
160      public boolean evaluate() throws Exception {
161        return AUTH_A.getMTime() > mtimeA;
162      }
163    });
164    Thread.sleep(1000);
165
166    // check it
167    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
168    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
169    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
170    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
171    assertTrue(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
172    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
173    assertTrue(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
174    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
175  }
176}