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.jupiter.api.Assertions.assertFalse;
021import static org.junit.jupiter.api.Assertions.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.HBaseTestingUtil;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.Waiter.Predicate;
031import org.apache.hadoop.hbase.security.User;
032import org.apache.hadoop.hbase.testclassification.MediumTests;
033import org.apache.hadoop.hbase.testclassification.SecurityTests;
034import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
035import org.junit.jupiter.api.AfterAll;
036import org.junit.jupiter.api.BeforeAll;
037import org.junit.jupiter.api.Tag;
038import org.junit.jupiter.api.Test;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
043import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
044
045/**
046 * Test the reading and writing of access permissions to and from zookeeper.
047 */
048@Tag(SecurityTests.TAG)
049@Tag(MediumTests.TAG)
050public class TestZKPermissionWatcher {
051
052  private static final Logger LOG = LoggerFactory.getLogger(TestZKPermissionWatcher.class);
053  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
054  private static AuthManager AUTH_A;
055  private static AuthManager AUTH_B;
056  private static ZKPermissionWatcher WATCHER_A;
057  private static ZKPermissionWatcher WATCHER_B;
058  private final static Abortable ABORTABLE = new Abortable() {
059    private final AtomicBoolean abort = new AtomicBoolean(false);
060
061    @Override
062    public void abort(String why, Throwable e) {
063      LOG.info(why, e);
064      abort.set(true);
065    }
066
067    @Override
068    public boolean isAborted() {
069      return abort.get();
070    }
071  };
072
073  private static TableName TEST_TABLE = TableName.valueOf("perms_test");
074
075  @BeforeAll
076  public static void beforeClass() throws Exception {
077    // setup configuration
078    Configuration conf = UTIL.getConfiguration();
079    SecureTestUtil.enableSecurity(conf);
080
081    // start minicluster
082    UTIL.startMiniCluster();
083    AUTH_A = new AuthManager(conf);
084    AUTH_B = new AuthManager(conf);
085    WATCHER_A = new ZKPermissionWatcher(
086      new ZKWatcher(conf, "TestZKPermissionsWatcher_1", ABORTABLE), AUTH_A, conf);
087    WATCHER_B = new ZKPermissionWatcher(
088      new ZKWatcher(conf, "TestZKPermissionsWatcher_2", ABORTABLE), AUTH_B, conf);
089    WATCHER_A.start();
090    WATCHER_B.start();
091  }
092
093  @AfterAll
094  public static void afterClass() throws Exception {
095    WATCHER_A.close();
096    WATCHER_B.close();
097    UTIL.shutdownMiniCluster();
098  }
099
100  @Test
101  public void testPermissionsWatcher() throws Exception {
102    Configuration conf = UTIL.getConfiguration();
103    User george = User.createUserForTesting(conf, "george", new String[] {});
104    User hubert = User.createUserForTesting(conf, "hubert", new String[] {});
105    ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create();
106
107    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
108    assertFalse(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
109    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
110    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
111
112    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
113    assertFalse(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
114    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
115    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
116
117    // update ACL: george RW
118    writeToZookeeper(WATCHER_A,
119      updatePermissions(permissions, george, Permission.Action.READ, Permission.Action.WRITE));
120    waitForModification(AUTH_B, 1000);
121
122    // check it
123    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
124    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
125    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
126    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
127    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
128    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
129    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
130    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
131
132    // update ACL: hubert R
133    writeToZookeeper(WATCHER_B, updatePermissions(permissions, hubert, Permission.Action.READ));
134    waitForModification(AUTH_A, 1000);
135
136    // check it
137    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
138    assertTrue(AUTH_A.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
139    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.READ));
140    assertTrue(AUTH_B.authorizeUserTable(george, TEST_TABLE, Permission.Action.WRITE));
141    assertTrue(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
142    assertFalse(AUTH_A.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
143    assertTrue(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.READ));
144    assertFalse(AUTH_B.authorizeUserTable(hubert, TEST_TABLE, Permission.Action.WRITE));
145  }
146
147  @Test
148  public void testRaceConditionOnPermissionUpdate() throws Exception {
149    Configuration conf = UTIL.getConfiguration();
150    User tom = User.createUserForTesting(conf, "tom", new String[] {});
151    User jerry = User.createUserForTesting(conf, "jerry", new String[] {});
152    ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create();
153
154    // update ACL: george RW
155    writeToZookeeper(WATCHER_A,
156      updatePermissions(permissions, tom, Permission.Action.READ, Permission.Action.WRITE));
157    waitForModification(AUTH_A, 1000);
158
159    // check it
160    assertTrue(AUTH_A.authorizeUserTable(tom, TEST_TABLE, Permission.Action.READ));
161    assertTrue(AUTH_A.authorizeUserTable(tom, TEST_TABLE, Permission.Action.WRITE));
162
163    // update ACL: hubert A
164    writeToZookeeper(WATCHER_A, updatePermissions(permissions, jerry, Permission.Action.ADMIN));
165    // intended not to waitForModification(AUTH_A, 1000);
166    // check george permission should not be updated/removed while updating permission for hubert
167    for (int i = 0; i < 5000; i++) {
168      assertTrue(AUTH_A.authorizeUserTable(tom, TEST_TABLE, Permission.Action.READ));
169      assertTrue(AUTH_A.authorizeUserTable(tom, TEST_TABLE, Permission.Action.WRITE));
170    }
171  }
172
173  private ListMultimap<String, UserPermission> updatePermissions(
174    ListMultimap<String, UserPermission> permissions, User user, Permission.Action... actions) {
175    List<UserPermission> acl = new ArrayList<>(1);
176    acl.add(new UserPermission(user.getShortName(),
177      Permission.newBuilder(TEST_TABLE).withActions(actions).build()));
178    permissions.putAll(user.getShortName(), acl);
179    return permissions;
180  }
181
182  private void writeToZookeeper(ZKPermissionWatcher watcher,
183    ListMultimap<String, UserPermission> permissions) {
184    byte[] serialized =
185      PermissionStorage.writePermissionsAsBytes(permissions, UTIL.getConfiguration());
186    watcher.writeToZookeeper(TEST_TABLE.getName(), serialized);
187  }
188
189  private void waitForModification(AuthManager authManager, long sleep) throws Exception {
190    final long mtime = authManager.getMTime();
191    // Wait for the update to propagate
192    UTIL.waitFor(10000, 100, new Predicate<Exception>() {
193      @Override
194      public boolean evaluate() throws Exception {
195        return authManager.getMTime() > mtime;
196      }
197    });
198    Thread.sleep(sleep);
199  }
200}