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.visibility; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.locks.ReentrantReadWriteLock; 029 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.AuthUtil; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.apache.hadoop.hbase.exceptions.DeserializationException; 034import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.MultiUserAuthorizations; 035import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.UserAuthorizations; 036import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabel; 037import org.apache.hadoop.hbase.util.Bytes; 038import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 039import org.apache.zookeeper.KeeperException; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * Maintains the cache for visibility labels and also uses the zookeeper to update the labels in the 045 * system. The cache updation happens based on the data change event that happens on the zookeeper 046 * znode for labels table 047 */ 048@InterfaceAudience.Private 049public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider { 050 051 private static final Logger LOG = LoggerFactory.getLogger(VisibilityLabelsCache.class); 052 private static final List<String> EMPTY_LIST = Collections.emptyList(); 053 private static final Set<Integer> EMPTY_SET = Collections.emptySet(); 054 private static VisibilityLabelsCache instance; 055 056 private ZKVisibilityLabelWatcher zkVisibilityWatcher; 057 private Map<String, Integer> labels = new HashMap<>(); 058 private Map<Integer, String> ordinalVsLabels = new HashMap<>(); 059 private Map<String, Set<Integer>> userAuths = new HashMap<>(); 060 private Map<String, Set<Integer>> groupAuths = new HashMap<>(); 061 062 /** 063 * This covers the members labels, ordinalVsLabels and userAuths 064 */ 065 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 066 067 private VisibilityLabelsCache(ZKWatcher watcher, Configuration conf) throws IOException { 068 zkVisibilityWatcher = new ZKVisibilityLabelWatcher(watcher, this, conf); 069 try { 070 zkVisibilityWatcher.start(); 071 } catch (KeeperException ke) { 072 LOG.error("ZooKeeper initialization failed", ke); 073 throw new IOException(ke); 074 } 075 } 076 077 /** 078 * Creates the singleton instance, if not yet present, and returns the same. 079 * @param watcher 080 * @param conf 081 * @return Singleton instance of VisibilityLabelsCache 082 * @throws IOException 083 */ 084 public synchronized static VisibilityLabelsCache createAndGet(ZKWatcher watcher, 085 Configuration conf) throws IOException { 086 // VisibilityLabelService#init() for different regions (in same RS) passes same instance of 087 // watcher as all get the instance from RS. 088 // watcher != instance.zkVisibilityWatcher.getWatcher() - This check is needed only in UTs with 089 // RS restart. It will be same JVM in which RS restarts and instance will be not null. But the 090 // watcher associated with existing instance will be stale as the restarted RS will have new 091 // watcher with it. 092 if (instance == null || watcher != instance.zkVisibilityWatcher.getWatcher()) { 093 instance = new VisibilityLabelsCache(watcher, conf); 094 } 095 return instance; 096 } 097 098 /** 099 * @return Singleton instance of VisibilityLabelsCache 100 * @throws IllegalStateException 101 * when this is called before calling 102 * {@link #createAndGet(ZKWatcher, Configuration)} 103 */ 104 public static VisibilityLabelsCache get() { 105 // By the time this method is called, the singleton instance of VisibilityLabelsCache should 106 // have been created. 107 if (instance == null) { 108 throw new IllegalStateException("VisibilityLabelsCache not yet instantiated"); 109 } 110 return instance; 111 } 112 113 public void refreshLabelsCache(byte[] data) throws IOException { 114 List<VisibilityLabel> visibilityLabels = null; 115 try { 116 visibilityLabels = VisibilityUtils.readLabelsFromZKData(data); 117 } catch (DeserializationException dse) { 118 throw new IOException(dse); 119 } 120 this.lock.writeLock().lock(); 121 try { 122 labels.clear(); 123 ordinalVsLabels.clear(); 124 for (VisibilityLabel visLabel : visibilityLabels) { 125 String label = Bytes.toString(visLabel.getLabel().toByteArray()); 126 labels.put(label, visLabel.getOrdinal()); 127 ordinalVsLabels.put(visLabel.getOrdinal(), label); 128 } 129 } finally { 130 this.lock.writeLock().unlock(); 131 } 132 } 133 134 public void refreshUserAuthsCache(byte[] data) throws IOException { 135 MultiUserAuthorizations multiUserAuths = null; 136 try { 137 multiUserAuths = VisibilityUtils.readUserAuthsFromZKData(data); 138 } catch (DeserializationException dse) { 139 throw new IOException(dse); 140 } 141 this.lock.writeLock().lock(); 142 try { 143 this.userAuths.clear(); 144 this.groupAuths.clear(); 145 for (UserAuthorizations userAuths : multiUserAuths.getUserAuthsList()) { 146 String user = Bytes.toString(userAuths.getUser().toByteArray()); 147 if (AuthUtil.isGroupPrincipal(user)) { 148 this.groupAuths.put(AuthUtil.getGroupName(user), new HashSet<>(userAuths.getAuthList())); 149 } else { 150 this.userAuths.put(user, new HashSet<>(userAuths.getAuthList())); 151 } 152 } 153 } finally { 154 this.lock.writeLock().unlock(); 155 } 156 } 157 158 /** 159 * @param label Not null label string 160 * @return The ordinal for the label. The ordinal starts from 1. Returns 0 when passed a non 161 * existing label. 162 */ 163 @Override 164 public int getLabelOrdinal(String label) { 165 Integer ordinal = null; 166 this.lock.readLock().lock(); 167 try { 168 ordinal = labels.get(label); 169 } finally { 170 this.lock.readLock().unlock(); 171 } 172 if (ordinal != null) { 173 return ordinal.intValue(); 174 } 175 // 0 denotes not available 176 return VisibilityConstants.NON_EXIST_LABEL_ORDINAL; 177 } 178 179 /** 180 * @param ordinal The ordinal of label which we are looking for. 181 * @return The label having the given ordinal. Returns <code>null</code> when no label exist in 182 * the system with given ordinal 183 */ 184 @Override 185 public String getLabel(int ordinal) { 186 this.lock.readLock().lock(); 187 try { 188 return this.ordinalVsLabels.get(ordinal); 189 } finally { 190 this.lock.readLock().unlock(); 191 } 192 } 193 194 /** 195 * @return The total number of visibility labels. 196 */ 197 public int getLabelsCount() { 198 this.lock.readLock().lock(); 199 try { 200 return this.labels.size(); 201 } finally { 202 this.lock.readLock().unlock(); 203 } 204 } 205 206 public List<String> getUserAuths(String user) { 207 this.lock.readLock().lock(); 208 try { 209 List<String> auths = EMPTY_LIST; 210 Set<Integer> authOrdinals = getUserAuthsAsOrdinals(user); 211 if (!authOrdinals.equals(EMPTY_SET)) { 212 auths = new ArrayList<>(authOrdinals.size()); 213 for (Integer authOrdinal : authOrdinals) { 214 auths.add(ordinalVsLabels.get(authOrdinal)); 215 } 216 } 217 return auths; 218 } finally { 219 this.lock.readLock().unlock(); 220 } 221 } 222 223 public List<String> getGroupAuths(String[] groups) { 224 this.lock.readLock().lock(); 225 try { 226 List<String> auths = EMPTY_LIST; 227 Set<Integer> authOrdinals = getGroupAuthsAsOrdinals(groups); 228 if (!authOrdinals.equals(EMPTY_SET)) { 229 auths = new ArrayList<>(authOrdinals.size()); 230 for (Integer authOrdinal : authOrdinals) { 231 auths.add(ordinalVsLabels.get(authOrdinal)); 232 } 233 } 234 return auths; 235 } finally { 236 this.lock.readLock().unlock(); 237 } 238 } 239 240 /** 241 * Returns the list of ordinals of labels associated with the user 242 * 243 * @param user Not null value. 244 * @return the list of ordinals 245 */ 246 public Set<Integer> getUserAuthsAsOrdinals(String user) { 247 this.lock.readLock().lock(); 248 try { 249 Set<Integer> auths = userAuths.get(user); 250 return (auths == null) ? EMPTY_SET : auths; 251 } finally { 252 this.lock.readLock().unlock(); 253 } 254 } 255 256 /** 257 * Returns the list of ordinals of labels associated with the groups 258 * 259 * @param groups 260 * @return the list of ordinals 261 */ 262 public Set<Integer> getGroupAuthsAsOrdinals(String[] groups) { 263 this.lock.readLock().lock(); 264 try { 265 Set<Integer> authOrdinals = new HashSet<>(); 266 if (groups != null && groups.length > 0) { 267 Set<Integer> groupAuthOrdinals = null; 268 for (String group : groups) { 269 groupAuthOrdinals = groupAuths.get(group); 270 if (groupAuthOrdinals != null && !groupAuthOrdinals.isEmpty()) { 271 authOrdinals.addAll(groupAuthOrdinals); 272 } 273 } 274 } 275 return (authOrdinals.isEmpty()) ? EMPTY_SET : authOrdinals; 276 } finally { 277 this.lock.readLock().unlock(); 278 } 279 } 280 281 public void writeToZookeeper(byte[] data, boolean labelsOrUserAuths) throws IOException { 282 // Update local state, then send it to zookeeper 283 if (labelsOrUserAuths) { 284 // True for labels 285 this.refreshLabelsCache(data); 286 } else { 287 // False for user auths 288 this.refreshUserAuthsCache(data); 289 } 290 this.zkVisibilityWatcher.writeToZookeeper(data, labelsOrUserAuths); 291 } 292}