View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.accumulo.server.security.handler;
18  
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  import java.util.Set;
24  import java.util.TreeSet;
25  
26  import org.apache.accumulo.core.Constants;
27  import org.apache.accumulo.core.client.AccumuloSecurityException;
28  import org.apache.accumulo.core.client.TableNotFoundException;
29  import org.apache.accumulo.core.security.SystemPermission;
30  import org.apache.accumulo.core.security.TablePermission;
31  import org.apache.accumulo.core.security.thrift.Credential;
32  import org.apache.accumulo.core.security.thrift.SecurityErrorCode;
33  import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
34  import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
35  import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
36  import org.apache.accumulo.server.zookeeper.ZooCache;
37  import org.apache.accumulo.server.zookeeper.ZooReaderWriter;
38  import org.apache.log4j.Logger;
39  import org.apache.zookeeper.KeeperException;
40  import org.apache.zookeeper.KeeperException.Code;
41  
42  /**
43   * 
44   */
45  public class ZKPermHandler implements PermissionHandler {
46    private static final Logger log = Logger.getLogger(ZKAuthorizor.class);
47    private static PermissionHandler zkPermHandlerInstance = null;
48    
49    private String ZKUserPath;
50    private final ZooCache zooCache;
51    private final String ZKUserSysPerms = "/System";
52    private final String ZKUserTablePerms = "/Tables";
53    
54    public static synchronized PermissionHandler getInstance() {
55      if (zkPermHandlerInstance == null)
56        zkPermHandlerInstance = new ZKPermHandler();
57      return zkPermHandlerInstance;
58    }
59    
60    public void initialize(String instanceId, boolean initialize) {
61      ZKUserPath = ZKSecurityTool.getInstancePath(instanceId) + "/users";
62    }
63    
64    public ZKPermHandler() {
65      zooCache = new ZooCache();
66    }
67    
68    @Override
69    public boolean hasTablePermission(String user, String table, TablePermission permission) {
70      byte[] serializedPerms;
71      try {
72        serializedPerms = ZooReaderWriter.getRetryingInstance().getData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, null);
73      } catch (KeeperException e) {
74        if (e.code() == Code.NONODE) {
75          return false;
76        }
77        log.warn("Unhandled KeeperException, failing closed for table permission check", e);
78        return false;
79      } catch (InterruptedException e) {
80        log.warn("Unhandled InterruptedException, failing closed for table permission check", e);
81        return false;
82      }
83      if (serializedPerms != null) {
84        return ZKSecurityTool.convertTablePermissions(serializedPerms).contains(permission);
85      }
86      return false;
87    }
88    
89    @Override
90    public boolean hasCachedTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException, TableNotFoundException {
91      byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table);
92      if (serializedPerms != null) {
93        return ZKSecurityTool.convertTablePermissions(serializedPerms).contains(permission);
94      }
95      return false;
96    }
97    
98    @Override
99    public void grantSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException {
100     try {
101       byte[] permBytes = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms);
102       Set<SystemPermission> perms;
103       if (permBytes == null) {
104         perms = new TreeSet<SystemPermission>();
105       } else {
106         perms = ZKSecurityTool.convertSystemPermissions(permBytes);
107       }
108       
109       if (perms.add(permission)) {
110         synchronized (zooCache) {
111           zooCache.clear();
112           ZooReaderWriter.getRetryingInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(perms),
113               NodeExistsPolicy.OVERWRITE);
114         }
115       }
116     } catch (KeeperException e) {
117       log.error(e, e);
118       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
119     } catch (InterruptedException e) {
120       log.error(e, e);
121       throw new RuntimeException(e);
122     }
123   }
124   
125   @Override
126   public void grantTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException {
127     Set<TablePermission> tablePerms;
128     byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table);
129     if (serializedPerms != null)
130       tablePerms = ZKSecurityTool.convertTablePermissions(serializedPerms);
131     else
132       tablePerms = new TreeSet<TablePermission>();
133     
134     try {
135       if (tablePerms.add(permission)) {
136         synchronized (zooCache) {
137           zooCache.clear(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table);
138           IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
139           zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, ZKSecurityTool.convertTablePermissions(tablePerms),
140               NodeExistsPolicy.OVERWRITE);
141         }
142       }
143     } catch (KeeperException e) {
144       log.error(e, e);
145       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
146     } catch (InterruptedException e) {
147       log.error(e, e);
148       throw new RuntimeException(e);
149     }
150   }
151   
152   @Override
153   public void revokeSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException {
154     byte[] sysPermBytes = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms);
155     
156     // User had no system permission, nothing to revoke.
157     if (sysPermBytes == null)
158       return;
159     
160     Set<SystemPermission> sysPerms = ZKSecurityTool.convertSystemPermissions(sysPermBytes);
161     
162     try {
163       if (sysPerms.remove(permission)) {
164         synchronized (zooCache) {
165           zooCache.clear();
166           ZooReaderWriter.getRetryingInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(sysPerms),
167               NodeExistsPolicy.OVERWRITE);
168         }
169       }
170     } catch (KeeperException e) {
171       log.error(e, e);
172       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
173     } catch (InterruptedException e) {
174       log.error(e, e);
175       throw new RuntimeException(e);
176     }
177   }
178   
179   @Override
180   public void revokeTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException {
181     byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table);
182     
183     // User had no table permission, nothing to revoke.
184     if (serializedPerms == null)
185       return;
186     
187     Set<TablePermission> tablePerms = ZKSecurityTool.convertTablePermissions(serializedPerms);
188     try {
189       if (tablePerms.remove(permission)) {
190         zooCache.clear();
191         IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
192         if (tablePerms.size() == 0)
193           zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, NodeMissingPolicy.SKIP);
194         else
195           zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, ZKSecurityTool.convertTablePermissions(tablePerms),
196               NodeExistsPolicy.OVERWRITE);
197       }
198     } catch (KeeperException e) {
199       log.error(e, e);
200       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
201     } catch (InterruptedException e) {
202       log.error(e, e);
203       throw new RuntimeException(e);
204     }
205   }
206   
207   @Override
208   public void cleanTablePermissions(String table) throws AccumuloSecurityException {
209     try {
210       synchronized (zooCache) {
211         zooCache.clear();
212         IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
213         for (String user : zooCache.getChildren(ZKUserPath))
214           zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, NodeMissingPolicy.SKIP);
215       }
216     } catch (KeeperException e) {
217       log.error(e, e);
218       throw new AccumuloSecurityException("unknownUser", SecurityErrorCode.CONNECTION_ERROR, e);
219     } catch (InterruptedException e) {
220       log.error(e, e);
221       throw new RuntimeException(e);
222     }
223   }
224   
225   @Override
226   public void initializeSecurity(Credential itw, String rootuser) throws AccumuloSecurityException {
227     IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
228     
229     // create the root user with all system privileges, no table privileges, and no record-level authorizations
230     Set<SystemPermission> rootPerms = new TreeSet<SystemPermission>();
231     for (SystemPermission p : SystemPermission.values())
232       rootPerms.add(p);
233     Map<String,Set<TablePermission>> tablePerms = new HashMap<String,Set<TablePermission>>();
234     // Allow the root user to flush the !METADATA table
235     tablePerms.put(Constants.METADATA_TABLE_ID, Collections.singleton(TablePermission.ALTER_TABLE));
236     
237     try {
238       // prep parent node of users with root username
239       if (!zoo.exists(ZKUserPath))
240         zoo.putPersistentData(ZKUserPath, rootuser.getBytes(), NodeExistsPolicy.FAIL);
241       
242       initUser(rootuser);
243       zoo.putPersistentData(ZKUserPath + "/" + rootuser + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(rootPerms), NodeExistsPolicy.FAIL);
244       for (Entry<String,Set<TablePermission>> entry : tablePerms.entrySet())
245         createTablePerm(rootuser, entry.getKey(), entry.getValue());
246     } catch (KeeperException e) {
247       log.error(e, e);
248       throw new RuntimeException(e);
249     } catch (InterruptedException e) {
250       log.error(e, e);
251       throw new RuntimeException(e);
252     }
253   }
254   
255   /**
256    * @param user
257    * @throws AccumuloSecurityException
258    */
259   public void initUser(String user) throws AccumuloSecurityException {
260     IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
261     try {
262       zoo.putPersistentData(ZKUserPath + "/" + user, new byte[0], NodeExistsPolicy.SKIP);
263       zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms, new byte[0], NodeExistsPolicy.SKIP);
264     } catch (KeeperException e) {
265       log.error(e, e);
266       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
267     } catch (InterruptedException e) {
268       log.error(e, e);
269       throw new RuntimeException(e);
270     }
271   }
272   
273   /**
274    * Sets up a new table configuration for the provided user/table. No checking for existence is done here, it should be done before calling.
275    */
276   private void createTablePerm(String user, String table, Set<TablePermission> perms) throws KeeperException, InterruptedException {
277     synchronized (zooCache) {
278       zooCache.clear();
279       ZooReaderWriter.getRetryingInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table,
280           ZKSecurityTool.convertTablePermissions(perms), NodeExistsPolicy.FAIL);
281     }
282   }
283   
284   @Override
285   public void cleanUser(String user) throws AccumuloSecurityException {
286     try {
287       synchronized (zooCache) {
288         IZooReaderWriter zoo = ZooReaderWriter.getRetryingInstance();
289         zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserSysPerms, NodeMissingPolicy.SKIP);
290         zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms, NodeMissingPolicy.SKIP);
291         zooCache.clear(ZKUserPath + "/" + user);
292       }
293     } catch (InterruptedException e) {
294       log.error(e, e);
295       throw new RuntimeException(e);
296     } catch (KeeperException e) {
297       log.error(e, e);
298       if (e.code().equals(KeeperException.Code.NONODE))
299         throw new AccumuloSecurityException(user, SecurityErrorCode.USER_DOESNT_EXIST, e);
300       throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e);
301       
302     }
303   }
304   
305   @Override
306   public boolean hasSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException {
307     byte[] perms;
308     try {
309       perms = ZooReaderWriter.getRetryingInstance().getData(ZKUserPath + "/" + user + ZKUserSysPerms, null);
310     } catch (KeeperException e) {
311       if (e.code() == Code.NONODE) {
312         return false;
313       }
314       log.warn("Unhandled KeeperException, failing closed for table permission check", e);
315       return false;
316     } catch (InterruptedException e) {
317       log.warn("Unhandled InterruptedException, failing closed for table permission check", e);
318       return false;
319     }
320     
321     if (perms == null)
322       return false;
323     return ZKSecurityTool.convertSystemPermissions(perms).contains(permission);
324   }
325   
326   @Override
327   public boolean hasCachedSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException {
328     byte[] perms = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms);
329     if (perms == null)
330       return false;
331     return ZKSecurityTool.convertSystemPermissions(perms).contains(permission);
332   }
333   
334   @Override
335   public boolean validSecurityHandlers(Authenticator authent, Authorizor author) {
336     return true;
337   }
338   
339   @Override
340   public void initTable(String table) throws AccumuloSecurityException {
341     // All proper housekeeping is done on delete and permission granting, no work needs to be done here
342   }
343 }