http://git-wip-us.apache.org/repos/asf/accumulo/blob/9b20a9d4/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java ---------------------------------------------------------------------- diff --cc server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java index d0e6aea,0000000..258080c mode 100644,000000..100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java @@@ -1,775 -1,0 +1,777 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.accumulo.server.security; + ++import static com.google.common.base.Charsets.UTF_8; ++ +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.NamespaceNotFoundException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.impl.SecurityOperationsImpl; +import org.apache.accumulo.core.client.impl.Namespaces; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.data.thrift.IterInfo; +import org.apache.accumulo.core.data.thrift.TColumn; +import org.apache.accumulo.core.data.thrift.TKeyExtent; +import org.apache.accumulo.core.data.thrift.TRange; +import org.apache.accumulo.core.master.thrift.FateOperation; +import org.apache.accumulo.core.metadata.MetadataTable; +import org.apache.accumulo.core.metadata.RootTable; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.core.security.Credentials; +import org.apache.accumulo.core.security.NamespacePermission; +import org.apache.accumulo.core.security.SystemPermission; +import org.apache.accumulo.core.security.TablePermission; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.server.client.HdfsZooInstance; +import org.apache.accumulo.server.conf.ServerConfiguration; +import org.apache.accumulo.server.security.handler.Authenticator; +import org.apache.accumulo.server.security.handler.Authorizor; +import org.apache.accumulo.server.security.handler.PermissionHandler; +import org.apache.accumulo.server.security.handler.ZKAuthenticator; +import org.apache.accumulo.server.security.handler.ZKAuthorizor; +import org.apache.accumulo.server.security.handler.ZKPermHandler; +import org.apache.accumulo.server.zookeeper.ZooCache; +import org.apache.hadoop.io.Text; +import org.apache.log4j.Logger; + +/** + * Utility class for performing various security operations with the appropriate checks + */ +public class SecurityOperation { + private static final Logger log = Logger.getLogger(SecurityOperationsImpl.class); + + protected Authorizor authorizor; + protected Authenticator authenticator; + protected PermissionHandler permHandle; + private static String rootUserName = null; + private final ZooCache zooCache; + private final String ZKUserPath; + + static SecurityOperation instance; + + public static synchronized SecurityOperation getInstance() { + String instanceId = HdfsZooInstance.getInstance().getInstanceID(); + return getInstance(instanceId, false); + } + + public static synchronized SecurityOperation getInstance(String instanceId, boolean initialize) { + if (instance == null) { + instance = new SecurityOperation(getAuthorizor(instanceId, initialize), getAuthenticator(instanceId, initialize), getPermHandler(instanceId, initialize), + instanceId); + } + return instance; + } + + protected static Authorizor getAuthorizor(String instanceId, boolean initialize) { + Authorizor toRet = ServerConfiguration.getSiteConfiguration().instantiateClassProperty(Property.INSTANCE_SECURITY_AUTHORIZOR, Authorizor.class, + ZKAuthorizor.getInstance()); + toRet.initialize(instanceId, initialize); + return toRet; + } + + protected static Authenticator getAuthenticator(String instanceId, boolean initialize) { + Authenticator toRet = ServerConfiguration.getSiteConfiguration().instantiateClassProperty(Property.INSTANCE_SECURITY_AUTHENTICATOR, Authenticator.class, + ZKAuthenticator.getInstance()); + toRet.initialize(instanceId, initialize); + return toRet; + } + + protected static PermissionHandler getPermHandler(String instanceId, boolean initialize) { + PermissionHandler toRet = ServerConfiguration.getSiteConfiguration().instantiateClassProperty(Property.INSTANCE_SECURITY_PERMISSION_HANDLER, + PermissionHandler.class, ZKPermHandler.getInstance()); + toRet.initialize(instanceId, initialize); + return toRet; + } + + protected SecurityOperation(String instanceId) { + ZKUserPath = Constants.ZROOT + "/" + instanceId + "/users"; + zooCache = new ZooCache(); + } + + public SecurityOperation(Authorizor author, Authenticator authent, PermissionHandler pm, String instanceId) { + this(instanceId); + authorizor = author; + authenticator = authent; + permHandle = pm; + + if (!authorizor.validSecurityHandlers(authenticator, pm) || !authenticator.validSecurityHandlers(authorizor, pm) + || !permHandle.validSecurityHandlers(authent, author)) + throw new RuntimeException(authorizor + ", " + authenticator + ", and " + pm + + " do not play nice with eachother. Please choose authentication and authorization mechanisms that are compatible with one another."); + } + + public void initializeSecurity(TCredentials credentials, String rootPrincipal, byte[] token) throws AccumuloSecurityException, ThriftSecurityException { + authenticate(credentials); + + if (!isSystemUser(credentials)) + throw new AccumuloSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + authenticator.initializeSecurity(credentials, rootPrincipal, token); + authorizor.initializeSecurity(credentials, rootPrincipal); + permHandle.initializeSecurity(credentials, rootPrincipal); + try { + permHandle.grantTablePermission(rootPrincipal, MetadataTable.ID, TablePermission.ALTER_TABLE); + } catch (TableNotFoundException e) { + // Shouldn't happen + throw new RuntimeException(e); + } + } + + public synchronized String getRootUsername() { + if (rootUserName == null) - rootUserName = new String(zooCache.get(ZKUserPath), Constants.UTF8); ++ rootUserName = new String(zooCache.get(ZKUserPath), UTF_8); + return rootUserName; + } + + public boolean isSystemUser(TCredentials credentials) { + return SystemCredentials.get().getToken().getClass().getName().equals(credentials.getTokenClassName()); + } + + protected void authenticate(TCredentials credentials) throws ThriftSecurityException { + if (!credentials.getInstanceId().equals(HdfsZooInstance.getInstance().getInstanceID())) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.INVALID_INSTANCEID); + + Credentials creds = Credentials.fromThrift(credentials); + if (isSystemUser(credentials)) { + if (!(SystemCredentials.get().equals(creds))) { + throw new ThriftSecurityException(creds.getPrincipal(), SecurityErrorCode.BAD_CREDENTIALS); + } + } else { + try { + if (!authenticator.authenticateUser(creds.getPrincipal(), creds.getToken())) { + throw new ThriftSecurityException(creds.getPrincipal(), SecurityErrorCode.BAD_CREDENTIALS); + } + } catch (AccumuloSecurityException e) { + log.debug(e); + throw e.asThriftException(); + } + } + } + + public boolean canAskAboutUser(TCredentials credentials, String user) throws ThriftSecurityException { + // Authentication done in canPerformSystemActions + if (!(canPerformSystemActions(credentials) || credentials.getPrincipal().equals(user))) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + return true; + } + + public boolean authenticateUser(TCredentials credentials, TCredentials toAuth) throws ThriftSecurityException { + canAskAboutUser(credentials, toAuth.getPrincipal()); + // User is already authenticated from canAskAboutUser + if (credentials.equals(toAuth)) + return true; + try { + Credentials toCreds = Credentials.fromThrift(toAuth); + return authenticator.authenticateUser(toCreds.getPrincipal(), toCreds.getToken()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public Authorizations getUserAuthorizations(TCredentials credentials, String user) throws ThriftSecurityException { + authenticate(credentials); + + targetUserExists(user); + + if (!credentials.getPrincipal().equals(user) && !hasSystemPermission(credentials, SystemPermission.SYSTEM, false)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + try { + return authorizor.getCachedUserAuthorizations(user); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public Authorizations getUserAuthorizations(TCredentials credentials) throws ThriftSecurityException { + // system user doesn't need record-level authorizations for the tables it reads + if (isSystemUser(credentials)) { + authenticate(credentials); + return Authorizations.EMPTY; + } + return getUserAuthorizations(credentials, credentials.getPrincipal()); + } + + public boolean userHasAuthorizations(TCredentials credentials, List<ByteBuffer> list) throws ThriftSecurityException { + authenticate(credentials); + + if (isSystemUser(credentials)) { + // system user doesn't need record-level authorizations for the tables it reads (for now) + return list.isEmpty(); + } + + try { + return authorizor.isValidAuthorizations(credentials.getPrincipal(), list); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + private boolean hasSystemPermission(TCredentials credentials, SystemPermission permission, boolean useCached) throws ThriftSecurityException { + return hasSystemPermissionWithNamespaceId(credentials, permission, null, useCached); + } + + /** + * Checks if a user has a system permission + * + * @return true if a user exists and has permission; false otherwise + */ + private boolean hasSystemPermissionWithNamespaceId(TCredentials credentials, SystemPermission permission, String namespaceId, boolean useCached) + throws ThriftSecurityException { + if (isSystemUser(credentials)) + return true; + + if (_hasSystemPermission(credentials.getPrincipal(), permission, useCached)) + return true; + if (namespaceId != null) { + return _hasNamespacePermission(credentials.getPrincipal(), namespaceId, NamespacePermission.getEquivalent(permission), useCached); + } + + return false; + } + + /** + * Checks if a user has a system permission<br/> + * This cannot check if a system user has permission. + * + * @return true if a user exists and has permission; false otherwise + */ + private boolean _hasSystemPermission(String user, SystemPermission permission, boolean useCached) throws ThriftSecurityException { + if (user.equals(getRootUsername())) + return true; + + targetUserExists(user); + + try { + if (useCached) + return permHandle.hasCachedSystemPermission(user, permission); + return permHandle.hasSystemPermission(user, permission); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + /** + * Checks if a user has a table permission + * + * @return true if a user exists and has permission; false otherwise + */ + protected boolean hasTablePermission(TCredentials credentials, String tableId, String namespaceId, TablePermission permission, boolean useCached) throws ThriftSecurityException { + if (isSystemUser(credentials)) + return true; + return _hasTablePermission(credentials.getPrincipal(), tableId, permission, useCached) + || _hasNamespacePermission(credentials.getPrincipal(), namespaceId, NamespacePermission.getEquivalent(permission), useCached); + } + + /** + * Checks if a user has a table permission<br/> + * This cannot check if a system user has permission. + * + * @return true if a user exists and has permission; false otherwise + */ + protected boolean _hasTablePermission(String user, String table, TablePermission permission, boolean useCached) throws ThriftSecurityException { + targetUserExists(user); + + if ((table.equals(MetadataTable.ID) || table.equals(RootTable.ID)) && permission.equals(TablePermission.READ)) + return true; + + try { + if (useCached) + return permHandle.hasCachedTablePermission(user, table, permission); + return permHandle.hasTablePermission(user, table, permission); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (TableNotFoundException e) { + throw new ThriftSecurityException(user, SecurityErrorCode.TABLE_DOESNT_EXIST); + } + } + + /** + * Checks if a user has a namespace permission<br/> + * This cannot check if a system user has permission. + * + * @return true if a user exists and has permission; false otherwise + */ + protected boolean _hasNamespacePermission(String user, String namespace, NamespacePermission permission, boolean useCached) throws ThriftSecurityException { + if (permission == null) + return false; + + targetUserExists(user); + + if (namespace.equals(Namespaces.ACCUMULO_NAMESPACE_ID) && permission.equals(NamespacePermission.READ)) + return true; + + try { + if (useCached) + return permHandle.hasCachedNamespacePermission(user, namespace, permission); + return permHandle.hasNamespacePermission(user, namespace, permission); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (NamespaceNotFoundException e) { + throw new ThriftSecurityException(user, SecurityErrorCode.NAMESPACE_DOESNT_EXIST); + } + } + + // some people just aren't allowed to ask about other users; here are those who can ask + private boolean canAskAboutOtherUsers(TCredentials credentials, String user) throws ThriftSecurityException { + authenticate(credentials); + return credentials.getPrincipal().equals(user) || hasSystemPermission(credentials, SystemPermission.SYSTEM, false) + || hasSystemPermission(credentials, SystemPermission.CREATE_USER, false) || hasSystemPermission(credentials, SystemPermission.ALTER_USER, false) + || hasSystemPermission(credentials, SystemPermission.DROP_USER, false); + } + + private void targetUserExists(String user) throws ThriftSecurityException { + if (user.equals(getRootUsername())) + return; + try { + if (!authenticator.userExists(user)) + throw new ThriftSecurityException(user, SecurityErrorCode.USER_DOESNT_EXIST); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public boolean canScan(TCredentials credentials, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasTablePermission(credentials, tableId, namespaceId, TablePermission.READ, true); + } + + public boolean canScan(TCredentials credentials, String tableId, String namespaceId, TRange range, List<TColumn> columns, List<IterInfo> ssiList, + Map<String,Map<String,String>> ssio, List<ByteBuffer> authorizations) throws ThriftSecurityException { + return canScan(credentials, tableId, namespaceId); + } + + public boolean canScan(TCredentials credentials, String table, String namespaceId, Map<TKeyExtent,List<TRange>> tbatch, List<TColumn> tcolumns, List<IterInfo> ssiList, + Map<String,Map<String,String>> ssio, List<ByteBuffer> authorizations) throws ThriftSecurityException { + return canScan(credentials, table, namespaceId); + } + + public boolean canWrite(TCredentials credentials, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasTablePermission(credentials, tableId, namespaceId, TablePermission.WRITE, true); + } + + public boolean canConditionallyUpdate(TCredentials credentials, String tableID, String namespaceId, List<ByteBuffer> authorizations) throws ThriftSecurityException { + + authenticate(credentials); + + return hasTablePermission(credentials, tableID, namespaceId, TablePermission.WRITE, true) && hasTablePermission(credentials, tableID, namespaceId, TablePermission.READ, true); + } + + public boolean canSplitTablet(TCredentials credentials, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermissionWithNamespaceId(credentials, SystemPermission.ALTER_TABLE, namespaceId, false) + || hasSystemPermissionWithNamespaceId(credentials, SystemPermission.SYSTEM, namespaceId, false) + || hasTablePermission(credentials, tableId, namespaceId, TablePermission.ALTER_TABLE, false); + } + + /** + * This is the check to perform any system action. This includes tserver's loading of a tablet, shutting the system down, or altering system properties. + */ + public boolean canPerformSystemActions(TCredentials credentials) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermission(credentials, SystemPermission.SYSTEM, false); + } + + public boolean canFlush(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasTablePermission(c, tableId, namespaceId, TablePermission.WRITE, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false); + } + + public boolean canAlterTable(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false) + || hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false); + } + + public boolean canCreateTable(TCredentials c, String table, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.CREATE_TABLE, namespaceId, false); + } + + public boolean canRenameTable(TCredentials c, String tableId, String oldTableName, String newTableName, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) + || hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false); + } + + public boolean canCloneTable(TCredentials c, String tableId, String tableName, String destinationNamespaceId, String srcNamespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.CREATE_TABLE, destinationNamespaceId, false) && hasTablePermission(c, tableId, srcNamespaceId, TablePermission.READ, false); + } + + public boolean canDeleteTable(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.DROP_TABLE, namespaceId, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.DROP_TABLE, false); + } + + public boolean canOnlineOfflineTable(TCredentials c, String tableId, FateOperation op, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.SYSTEM, namespaceId, false) + || hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) + || hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false); + } + + public boolean canMerge(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.SYSTEM, namespaceId, false) + || hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) + || hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false); + } + + public boolean canDeleteRange(TCredentials c, String tableId, String tableName, Text startRow, Text endRow, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.SYSTEM, namespaceId, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.WRITE, false); + } + + public boolean canBulkImport(TCredentials c, String tableId, String tableName, String dir, String failDir, String namespaceId) throws ThriftSecurityException { + return canBulkImport(c, tableId, namespaceId); + } + + public boolean canBulkImport(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasTablePermission(c, tableId, namespaceId, TablePermission.BULK_IMPORT, false); + } + + public boolean canCompact(TCredentials c, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) + || hasTablePermission(c, tableId, namespaceId, TablePermission.ALTER_TABLE, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.WRITE, false); + } + + public boolean canChangeAuthorizations(TCredentials c, String user) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermission(c, SystemPermission.ALTER_USER, false); + } + + public boolean canChangePassword(TCredentials c, String user) throws ThriftSecurityException { + authenticate(c); + return c.getPrincipal().equals(user) || hasSystemPermission(c, SystemPermission.ALTER_USER, false); + } + + public boolean canCreateUser(TCredentials c, String user) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermission(c, SystemPermission.CREATE_USER, false); + } + + public boolean canDropUser(TCredentials c, String user) throws ThriftSecurityException { + authenticate(c); + if (user.equals(getRootUsername())) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + return hasSystemPermission(c, SystemPermission.DROP_USER, false); + } + + public boolean canGrantSystem(TCredentials c, String user, SystemPermission sysPerm) throws ThriftSecurityException { + authenticate(c); + // can't grant GRANT + if (sysPerm.equals(SystemPermission.GRANT)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.GRANT_INVALID); + return hasSystemPermission(c, SystemPermission.GRANT, false); + } + + public boolean canGrantTable(TCredentials c, String user, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.GRANT, false); + } + + public boolean canGrantNamespace(TCredentials c, String user, String namespace) throws ThriftSecurityException { + return canModifyNamespacePermission(c, user, namespace); + } + + private boolean canModifyNamespacePermission(TCredentials c, String user, String namespace) throws ThriftSecurityException { + authenticate(c); + // The one case where Table/SystemPermission -> NamespacePermission breaks down. The alternative is to make SystemPermission.ALTER_NAMESPACE provide + // NamespacePermission.GRANT & ALTER_NAMESPACE, but then it would cause some permission checks to succeed with GRANT when they shouldn't + + // This is a bit hackier then I (vines) wanted, but I think this one hackiness makes the overall SecurityOperations more succinct. + return hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_NAMESPACE, namespace, false) + || hasNamespacePermission(c, c.principal, namespace, NamespacePermission.GRANT); + } + + public boolean canRevokeSystem(TCredentials c, String user, SystemPermission sysPerm) throws ThriftSecurityException { + authenticate(c); + // can't modify root user + if (user.equals(getRootUsername())) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + // can't revoke GRANT + if (sysPerm.equals(SystemPermission.GRANT)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.GRANT_INVALID); + + return hasSystemPermission(c, SystemPermission.GRANT, false); + } + + public boolean canRevokeTable(TCredentials c, String user, String tableId, String namespaceId) throws ThriftSecurityException { + authenticate(c); + return hasSystemPermissionWithNamespaceId(c, SystemPermission.ALTER_TABLE, namespaceId, false) || hasTablePermission(c, tableId, namespaceId, TablePermission.GRANT, false); + } + + public boolean canRevokeNamespace(TCredentials c, String user, String namespace) throws ThriftSecurityException { + return canModifyNamespacePermission(c, user, namespace); + } + + public void changeAuthorizations(TCredentials credentials, String user, Authorizations authorizations) throws ThriftSecurityException { + if (!canChangeAuthorizations(credentials, user)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + authorizor.changeAuthorizations(user, authorizations); + log.info("Changed authorizations for user " + user + " at the request of user " + credentials.getPrincipal()); + } catch (AccumuloSecurityException ase) { + throw ase.asThriftException(); + } + } + + public void changePassword(TCredentials credentials, Credentials toChange) throws ThriftSecurityException { + if (!canChangePassword(credentials, toChange.getPrincipal())) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + try { + AuthenticationToken token = toChange.getToken(); + authenticator.changePassword(toChange.getPrincipal(), token); + log.info("Changed password for user " + toChange.getPrincipal() + " at the request of user " + credentials.getPrincipal()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public void createUser(TCredentials credentials, Credentials newUser, Authorizations authorizations) throws ThriftSecurityException { + if (!canCreateUser(credentials, newUser.getPrincipal())) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + try { + AuthenticationToken token = newUser.getToken(); + authenticator.createUser(newUser.getPrincipal(), token); + authorizor.initUser(newUser.getPrincipal()); + permHandle.initUser(newUser.getPrincipal()); + log.info("Created user " + newUser.getPrincipal() + " at the request of user " + credentials.getPrincipal()); + if (canChangeAuthorizations(credentials, newUser.getPrincipal())) + authorizor.changeAuthorizations(newUser.getPrincipal(), authorizations); + } catch (AccumuloSecurityException ase) { + throw ase.asThriftException(); + } + } + + public void dropUser(TCredentials credentials, String user) throws ThriftSecurityException { + if (!canDropUser(credentials, user)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + try { + authorizor.dropUser(user); + authenticator.dropUser(user); + permHandle.cleanUser(user); + log.info("Deleted user " + user + " at the request of user " + credentials.getPrincipal()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public void grantSystemPermission(TCredentials credentials, String user, SystemPermission permissionById) throws ThriftSecurityException { + if (!canGrantSystem(credentials, user, permissionById)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.grantSystemPermission(user, permissionById); + log.info("Granted system permission " + permissionById + " for user " + user + " at the request of user " + credentials.getPrincipal()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public void grantTablePermission(TCredentials c, String user, String tableId, TablePermission permission, String namespaceId) throws ThriftSecurityException { + if (!canGrantTable(c, user, tableId, namespaceId)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.grantTablePermission(user, tableId, permission); + log.info("Granted table permission " + permission + " for user " + user + " on the table " + tableId + " at the request of user " + c.getPrincipal()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (TableNotFoundException e) { + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.TABLE_DOESNT_EXIST); + } + } + + public void grantNamespacePermission(TCredentials c, String user, String namespace, NamespacePermission permission) throws ThriftSecurityException { + if (!canGrantNamespace(c, user, namespace)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.grantNamespacePermission(user, namespace, permission); + log.info("Granted namespace permission " + permission + " for user " + user + " on the namespace " + namespace + " at the request of user " + + c.getPrincipal()); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (NamespaceNotFoundException e) { + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.NAMESPACE_DOESNT_EXIST); + } + } + + public void revokeSystemPermission(TCredentials credentials, String user, SystemPermission permission) throws ThriftSecurityException { + if (!canRevokeSystem(credentials, user, permission)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.revokeSystemPermission(user, permission); + log.info("Revoked system permission " + permission + " for user " + user + " at the request of user " + credentials.getPrincipal()); + + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public void revokeTablePermission(TCredentials c, String user, String tableId, TablePermission permission, String namespaceId) throws ThriftSecurityException { + if (!canRevokeTable(c, user, tableId, namespaceId)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.revokeTablePermission(user, tableId, permission); + log.info("Revoked table permission " + permission + " for user " + user + " on the table " + tableId + " at the request of user " + c.getPrincipal()); + + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (TableNotFoundException e) { + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.TABLE_DOESNT_EXIST); + } + } + + public void revokeNamespacePermission(TCredentials c, String user, String namespace, NamespacePermission permission) throws ThriftSecurityException { + if (!canRevokeNamespace(c, user, namespace)) + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + + targetUserExists(user); + + try { + permHandle.revokeNamespacePermission(user, namespace, permission); + log.info("Revoked namespace permission " + permission + " for user " + user + " on the namespace " + namespace + " at the request of user " + + c.getPrincipal()); + + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } catch (NamespaceNotFoundException e) { + throw new ThriftSecurityException(c.getPrincipal(), SecurityErrorCode.NAMESPACE_DOESNT_EXIST); + } + } + + public boolean hasSystemPermission(TCredentials credentials, String user, SystemPermission permissionById) throws ThriftSecurityException { + if (!canAskAboutOtherUsers(credentials, user)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + return _hasSystemPermission(user, permissionById, false); + } + + public boolean hasTablePermission(TCredentials credentials, String user, String tableId, TablePermission permissionById) throws ThriftSecurityException { + if (!canAskAboutOtherUsers(credentials, user)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + return _hasTablePermission(user, tableId, permissionById, false); + } + + public boolean hasNamespacePermission(TCredentials credentials, String user, String namespace, NamespacePermission permissionById) + throws ThriftSecurityException { + if (!canAskAboutOtherUsers(credentials, user)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + return _hasNamespacePermission(user, namespace, permissionById, false); + } + + public Set<String> listUsers(TCredentials credentials) throws ThriftSecurityException { + authenticate(credentials); + try { + return authenticator.listUsers(); + } catch (AccumuloSecurityException e) { + throw e.asThriftException(); + } + } + + public void deleteTable(TCredentials credentials, String tableId, String namespaceId) throws ThriftSecurityException { + if (!canDeleteTable(credentials, tableId, namespaceId)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + try { + permHandle.cleanTablePermissions(tableId); + } catch (AccumuloSecurityException e) { + e.setUser(credentials.getPrincipal()); + throw e.asThriftException(); + } catch (TableNotFoundException e) { + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.TABLE_DOESNT_EXIST); + } + } + + public void deleteNamespace(TCredentials credentials, String namespace) throws ThriftSecurityException { + if (!canDeleteNamespace(credentials, namespace)) + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); + try { + permHandle.cleanNamespacePermissions(namespace); + } catch (AccumuloSecurityException e) { + e.setUser(credentials.getPrincipal()); + throw e.asThriftException(); + } catch (NamespaceNotFoundException e) { + throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.NAMESPACE_DOESNT_EXIST); + } + } + + public boolean canExport(TCredentials credentials, String tableId, String tableName, String exportDir, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasTablePermission(credentials, tableId, namespaceId, TablePermission.READ, false); + } + + public boolean canImport(TCredentials credentials, String tableName, String importDir, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermissionWithNamespaceId(credentials, SystemPermission.CREATE_TABLE, namespaceId, false); + } + + public boolean canAlterNamespace(TCredentials credentials, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermissionWithNamespaceId(credentials, SystemPermission.ALTER_NAMESPACE, namespaceId, false); + } + + public boolean canCreateNamespace(TCredentials credentials, String namespace) throws ThriftSecurityException { + return canCreateNamespace(credentials); + } + + private boolean canCreateNamespace(TCredentials credentials) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermission(credentials, SystemPermission.CREATE_NAMESPACE, false); + } + + public boolean canDeleteNamespace(TCredentials credentials, String namespaceId) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermissionWithNamespaceId(credentials, SystemPermission.DROP_NAMESPACE, namespaceId, false); + } + + public boolean canRenameNamespace(TCredentials credentials, String namespaceId, String oldName, String newName) throws ThriftSecurityException { + authenticate(credentials); + return hasSystemPermissionWithNamespaceId(credentials, SystemPermission.ALTER_NAMESPACE, namespaceId, false); + } + +}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/9b20a9d4/server/base/src/main/java/org/apache/accumulo/server/security/SystemCredentials.java ---------------------------------------------------------------------- diff --cc server/base/src/main/java/org/apache/accumulo/server/security/SystemCredentials.java index 19e7ff7,0000000..872fe11 mode 100644,000000..100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/SystemCredentials.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/SystemCredentials.java @@@ -1,149 -1,0 +1,151 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.accumulo.server.security; + ++import static com.google.common.base.Charsets.UTF_8; ++ +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecurityPermission; +import java.util.Map.Entry; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.security.Credentials; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.core.util.Base64; +import org.apache.accumulo.server.ServerConstants; +import org.apache.accumulo.server.client.HdfsZooInstance; +import org.apache.accumulo.server.conf.ServerConfiguration; +import org.apache.hadoop.io.Writable; + +/** + * Credentials for the system services. + * + * @since 1.6.0 + */ +public final class SystemCredentials extends Credentials { + + private static final SecurityPermission SYSTEM_CREDENTIALS_PERMISSION = new SecurityPermission("systemCredentialsPermission"); + + private static SystemCredentials SYSTEM_CREDS = null; + private static final String SYSTEM_PRINCIPAL = "!SYSTEM"; + + private final TCredentials AS_THRIFT; + + SystemCredentials(Instance instance) { + super(SYSTEM_PRINCIPAL, SystemToken.get(instance)); + AS_THRIFT = super.toThrift(instance); + } + + public static SystemCredentials get() { + check_permission(); + if (SYSTEM_CREDS == null) { + SYSTEM_CREDS = new SystemCredentials(HdfsZooInstance.getInstance()); + } + return SYSTEM_CREDS; + } + + private static void check_permission() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(SYSTEM_CREDENTIALS_PERMISSION); + } + } + + public static SystemCredentials get(Instance instance) { + check_permission(); + /* Special case to avoid duplicating SYSTEM_CREDS */ + if (null != SYSTEM_CREDS) { + if (SYSTEM_CREDS.AS_THRIFT.getInstanceId().equals(instance.getInstanceID())) { + return SYSTEM_CREDS; + } + } + return new SystemCredentials(instance); + } + + @Override + public TCredentials toThrift(Instance instance) { + if (!AS_THRIFT.getInstanceId().equals(instance.getInstanceID())) + throw new IllegalArgumentException("Unexpected instance used for " + SystemCredentials.class.getSimpleName() + ": " + instance.getInstanceID()); + return AS_THRIFT; + } + + /** + * An {@link AuthenticationToken} type for Accumulo servers for inter-server communication. + * + * @since 1.6.0 + */ + public static final class SystemToken extends PasswordToken { + + /** + * A Constructor for {@link Writable}. + */ + public SystemToken() {} + + private SystemToken(byte[] systemPassword) { + super(systemPassword); + } + + private static SystemToken get(Instance instance) { - byte[] instanceIdBytes = instance.getInstanceID().getBytes(Constants.UTF8); ++ byte[] instanceIdBytes = instance.getInstanceID().getBytes(UTF_8); + byte[] confChecksum; + MessageDigest md; + try { + md = MessageDigest.getInstance(Constants.PW_HASH_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to compute configuration checksum", e); + } + + // seed the config with the version and instance id, so at least it's not empty - md.update(ServerConstants.WIRE_VERSION.toString().getBytes(Constants.UTF8)); ++ md.update(ServerConstants.WIRE_VERSION.toString().getBytes(UTF_8)); + md.update(instanceIdBytes); + + for (Entry<String,String> entry : ServerConfiguration.getSiteConfiguration()) { + // only include instance properties + if (entry.getKey().startsWith(Property.INSTANCE_PREFIX.toString())) { - md.update(entry.getKey().getBytes(Constants.UTF8)); - md.update(entry.getValue().getBytes(Constants.UTF8)); ++ md.update(entry.getKey().getBytes(UTF_8)); ++ md.update(entry.getValue().getBytes(UTF_8)); + } + } + confChecksum = md.digest(); + + int wireVersion = ServerConstants.WIRE_VERSION; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(3 * (Integer.SIZE / Byte.SIZE) + instanceIdBytes.length + confChecksum.length); + DataOutputStream out = new DataOutputStream(bytes); + try { + out.write(wireVersion * -1); + out.write(instanceIdBytes.length); + out.write(instanceIdBytes); + out.write(confChecksum.length); + out.write(confChecksum); + } catch (IOException e) { + // this is impossible with ByteArrayOutputStream; crash hard if this happens + throw new RuntimeException(e); + } + return new SystemToken(Base64.encodeBase64(bytes.toByteArray())); + } + } + +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/9b20a9d4/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthenticator.java ---------------------------------------------------------------------- diff --cc server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthenticator.java index 5f6ff71,0000000..fa1632c mode 100644,000000..100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthenticator.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthenticator.java @@@ -1,214 -1,0 +1,216 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.accumulo.server.security.handler; + ++import static com.google.common.base.Charsets.UTF_8; ++ +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.fate.zookeeper.IZooReaderWriter; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy; +import org.apache.accumulo.server.zookeeper.ZooCache; +import org.apache.accumulo.server.zookeeper.ZooReaderWriter; +import org.apache.log4j.Logger; +import org.apache.zookeeper.KeeperException; + +// Utility class for adding all authentication info into ZK +public final class ZKAuthenticator implements Authenticator { + static final Logger log = Logger.getLogger(ZKAuthenticator.class); + private static Authenticator zkAuthenticatorInstance = null; + + private String ZKUserPath; + private final ZooCache zooCache; + + public static synchronized Authenticator getInstance() { + if (zkAuthenticatorInstance == null) + zkAuthenticatorInstance = new ZKAuthenticator(); + return zkAuthenticatorInstance; + } + + public ZKAuthenticator() { + zooCache = new ZooCache(); + } + + @Override + public void initialize(String instanceId, boolean initialize) { + ZKUserPath = Constants.ZROOT + "/" + instanceId + "/users"; + } + + @Override + public void initializeSecurity(TCredentials credentials, String principal, byte[] token) throws AccumuloSecurityException { + try { + // remove old settings from zookeeper first, if any + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + synchronized (zooCache) { + zooCache.clear(); + if (zoo.exists(ZKUserPath)) { + zoo.recursiveDelete(ZKUserPath, NodeMissingPolicy.SKIP); + log.info("Removed " + ZKUserPath + "/" + " from zookeeper"); + } + + // prep parent node of users with root username - zoo.putPersistentData(ZKUserPath, principal.getBytes(Constants.UTF8), NodeExistsPolicy.FAIL); ++ zoo.putPersistentData(ZKUserPath, principal.getBytes(UTF_8), NodeExistsPolicy.FAIL); + + constructUser(principal, ZKSecurityTool.createPass(token)); + } + } catch (KeeperException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (AccumuloException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + /** + * Sets up the user in ZK for the provided user. No checking for existence is done here, it should be done before calling. + */ + private void constructUser(String user, byte[] pass) throws KeeperException, InterruptedException { + synchronized (zooCache) { + zooCache.clear(); + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + zoo.putPrivatePersistentData(ZKUserPath + "/" + user, pass, NodeExistsPolicy.FAIL); + } + } + + @Override + public Set<String> listUsers() { + return new TreeSet<String>(zooCache.getChildren(ZKUserPath)); + } + + /** + * Creates a user with no permissions whatsoever + */ + @Override + public void createUser(String principal, AuthenticationToken token) throws AccumuloSecurityException { + try { + if (!(token instanceof PasswordToken)) + throw new AccumuloSecurityException(principal, SecurityErrorCode.INVALID_TOKEN); + PasswordToken pt = (PasswordToken) token; + constructUser(principal, ZKSecurityTool.createPass(pt.getPassword())); + } catch (KeeperException e) { + if (e.code().equals(KeeperException.Code.NODEEXISTS)) + throw new AccumuloSecurityException(principal, SecurityErrorCode.USER_EXISTS, e); + throw new AccumuloSecurityException(principal, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (AccumuloException e) { + log.error(e, e); + throw new AccumuloSecurityException(principal, SecurityErrorCode.DEFAULT_SECURITY_ERROR, e); + } + } + + @Override + public void dropUser(String user) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().recursiveDelete(ZKUserPath + "/" + user, NodeMissingPolicy.FAIL); + } + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (KeeperException e) { + if (e.code().equals(KeeperException.Code.NONODE)) + throw new AccumuloSecurityException(user, SecurityErrorCode.USER_DOESNT_EXIST, e); + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } + } + + @Override + public void changePassword(String principal, AuthenticationToken token) throws AccumuloSecurityException { + if (!(token instanceof PasswordToken)) + throw new AccumuloSecurityException(principal, SecurityErrorCode.INVALID_TOKEN); + PasswordToken pt = (PasswordToken) token; + if (userExists(principal)) { + try { + synchronized (zooCache) { + zooCache.clear(ZKUserPath + "/" + principal); + ZooReaderWriter.getInstance().putPrivatePersistentData(ZKUserPath + "/" + principal, ZKSecurityTool.createPass(pt.getPassword()), + NodeExistsPolicy.OVERWRITE); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(principal, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (AccumuloException e) { + log.error(e, e); + throw new AccumuloSecurityException(principal, SecurityErrorCode.DEFAULT_SECURITY_ERROR, e); + } + } else + throw new AccumuloSecurityException(principal, SecurityErrorCode.USER_DOESNT_EXIST); // user doesn't exist + } + + /** + * Checks if a user exists + */ + @Override + public boolean userExists(String user) { + return zooCache.get(ZKUserPath + "/" + user) != null; + } + + @Override + public boolean validSecurityHandlers(Authorizor auth, PermissionHandler pm) { + return true; + } + + @Override + public boolean authenticateUser(String principal, AuthenticationToken token) throws AccumuloSecurityException { + if (!(token instanceof PasswordToken)) + throw new AccumuloSecurityException(principal, SecurityErrorCode.INVALID_TOKEN); + PasswordToken pt = (PasswordToken) token; + byte[] pass; + String zpath = ZKUserPath + "/" + principal; + pass = zooCache.get(zpath); + boolean result = ZKSecurityTool.checkPass(pt.getPassword(), pass); + if (!result) { + zooCache.clear(zpath); + pass = zooCache.get(zpath); + result = ZKSecurityTool.checkPass(pt.getPassword(), pass); + } + return result; + } + + @Override + public Set<Class<? extends AuthenticationToken>> getSupportedTokenTypes() { + Set<Class<? extends AuthenticationToken>> cs = new HashSet<Class<? extends AuthenticationToken>>(); + cs.add(PasswordToken.class); + return cs; + } + + @Override + public boolean validTokenClass(String tokenClass) { + return tokenClass.equals(PasswordToken.class.getName()); + } +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/9b20a9d4/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthorizor.java ---------------------------------------------------------------------- diff --cc server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthorizor.java index 75b73fc,0000000..78597ad mode 100644,000000..100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthorizor.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKAuthorizor.java @@@ -1,171 -1,0 +1,172 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.accumulo.server.security.handler; + ++import static com.google.common.base.Charsets.UTF_8; ++ +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + - import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.metadata.MetadataTable; +import org.apache.accumulo.core.metadata.RootTable; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.core.security.SystemPermission; +import org.apache.accumulo.core.security.TablePermission; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.fate.zookeeper.IZooReaderWriter; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy; +import org.apache.accumulo.server.zookeeper.ZooCache; +import org.apache.accumulo.server.zookeeper.ZooReaderWriter; +import org.apache.log4j.Logger; +import org.apache.zookeeper.KeeperException; + +public class ZKAuthorizor implements Authorizor { + private static final Logger log = Logger.getLogger(ZKAuthorizor.class); + private static Authorizor zkAuthorizorInstance = null; + + private final String ZKUserAuths = "/Authorizations"; + + private String ZKUserPath; + private final ZooCache zooCache; + + public static synchronized Authorizor getInstance() { + if (zkAuthorizorInstance == null) + zkAuthorizorInstance = new ZKAuthorizor(); + return zkAuthorizorInstance; + } + + public ZKAuthorizor() { + zooCache = new ZooCache(); + } + + @Override + public void initialize(String instanceId, boolean initialize) { + ZKUserPath = ZKSecurityTool.getInstancePath(instanceId) + "/users"; + } + + @Override + public Authorizations getCachedUserAuthorizations(String user) { + byte[] authsBytes = zooCache.get(ZKUserPath + "/" + user + ZKUserAuths); + if (authsBytes != null) + return ZKSecurityTool.convertAuthorizations(authsBytes); + return Authorizations.EMPTY; + } + + @Override + public boolean validSecurityHandlers(Authenticator auth, PermissionHandler pm) { + return true; + } + + @Override + public void initializeSecurity(TCredentials itw, String rootuser) throws AccumuloSecurityException { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + + // create the root user with all system privileges, no table privileges, and no record-level authorizations + Set<SystemPermission> rootPerms = new TreeSet<SystemPermission>(); + for (SystemPermission p : SystemPermission.values()) + rootPerms.add(p); + Map<String,Set<TablePermission>> tablePerms = new HashMap<String,Set<TablePermission>>(); + // Allow the root user to flush the metadata tables + tablePerms.put(MetadataTable.ID, Collections.singleton(TablePermission.ALTER_TABLE)); + tablePerms.put(RootTable.ID, Collections.singleton(TablePermission.ALTER_TABLE)); + + try { + // prep parent node of users with root username + if (!zoo.exists(ZKUserPath)) - zoo.putPersistentData(ZKUserPath, rootuser.getBytes(Constants.UTF8), NodeExistsPolicy.FAIL); ++ zoo.putPersistentData(ZKUserPath, rootuser.getBytes(UTF_8), NodeExistsPolicy.FAIL); + + initUser(rootuser); + zoo.putPersistentData(ZKUserPath + "/" + rootuser + ZKUserAuths, ZKSecurityTool.convertAuthorizations(Authorizations.EMPTY), NodeExistsPolicy.FAIL); + } catch (KeeperException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void initUser(String user) throws AccumuloSecurityException { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + try { + zoo.putPersistentData(ZKUserPath + "/" + user, new byte[0], NodeExistsPolicy.SKIP); + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void dropUser(String user) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserAuths, NodeMissingPolicy.SKIP); + zooCache.clear(ZKUserPath + "/" + user); + } + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (KeeperException e) { + log.error(e, e); + if (e.code().equals(KeeperException.Code.NONODE)) + throw new AccumuloSecurityException(user, SecurityErrorCode.USER_DOESNT_EXIST, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + + } + } + + @Override + public void changeAuthorizations(String user, Authorizations authorizations) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserAuths, ZKSecurityTool.convertAuthorizations(authorizations), + NodeExistsPolicy.OVERWRITE); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public boolean isValidAuthorizations(String user, List<ByteBuffer> auths) throws AccumuloSecurityException { + Collection<ByteBuffer> userauths = getCachedUserAuthorizations(user).getAuthorizationsBB(); + for (ByteBuffer auth : auths) + if (!userauths.contains(auth)) + return false; + return true; + } + +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/9b20a9d4/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKPermHandler.java ---------------------------------------------------------------------- diff --cc server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKPermHandler.java index be4806b,0000000..41f880d mode 100644,000000..100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKPermHandler.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/handler/ZKPermHandler.java @@@ -1,515 -1,0 +1,516 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.accumulo.server.security.handler; + ++import static com.google.common.base.Charsets.UTF_8; ++ +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + - import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.NamespaceNotFoundException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.impl.Namespaces; +import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; +import org.apache.accumulo.core.metadata.MetadataTable; +import org.apache.accumulo.core.metadata.RootTable; +import org.apache.accumulo.core.security.NamespacePermission; +import org.apache.accumulo.core.security.SystemPermission; +import org.apache.accumulo.core.security.TablePermission; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.fate.zookeeper.IZooReaderWriter; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy; +import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy; +import org.apache.accumulo.server.zookeeper.ZooCache; +import org.apache.accumulo.server.zookeeper.ZooReaderWriter; +import org.apache.log4j.Logger; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.Code; + +/** + * + */ +public class ZKPermHandler implements PermissionHandler { + private static final Logger log = Logger.getLogger(ZKAuthorizor.class); + private static PermissionHandler zkPermHandlerInstance = null; + + private String ZKUserPath; + private String ZKTablePath; + private String ZKNamespacePath; + private final ZooCache zooCache; + private final String ZKUserSysPerms = "/System"; + private final String ZKUserTablePerms = "/Tables"; + private final String ZKUserNamespacePerms = "/Namespaces"; + + public static synchronized PermissionHandler getInstance() { + if (zkPermHandlerInstance == null) + zkPermHandlerInstance = new ZKPermHandler(); + return zkPermHandlerInstance; + } + + @Override + public void initialize(String instanceId, boolean initialize) { + ZKUserPath = ZKSecurityTool.getInstancePath(instanceId) + "/users"; + ZKTablePath = ZKSecurityTool.getInstancePath(instanceId) + "/tables"; + ZKNamespacePath = ZKSecurityTool.getInstancePath(instanceId) + "/namespaces"; + } + + public ZKPermHandler() { + zooCache = new ZooCache(); + } + + @Override + public boolean hasTablePermission(String user, String table, TablePermission permission) throws TableNotFoundException { + byte[] serializedPerms; + final ZooReaderWriter zrw = ZooReaderWriter.getInstance(); + try { + String path = ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table; + zrw.sync(path); + serializedPerms = zrw.getData(path, null); + } catch (KeeperException e) { + if (e.code() == Code.NONODE) { + // maybe the table was just deleted? + try { + // check for existence: + zrw.getData(ZKTablePath + "/" + table, null); + // it's there, you don't have permission + return false; + } catch (InterruptedException ex) { + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + return false; + } catch (KeeperException ex) { + // not there, throw an informative exception + if (e.code() == Code.NONODE) { + throw new TableNotFoundException(null, table, "while checking permissions"); + } + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + } + return false; + } + log.warn("Unhandled KeeperException, failing closed for table permission check", e); + return false; + } catch (InterruptedException e) { + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + return false; + } + if (serializedPerms != null) { + return ZKSecurityTool.convertTablePermissions(serializedPerms).contains(permission); + } + return false; + } + + @Override + public boolean hasCachedTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException, TableNotFoundException { + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table); + if (serializedPerms != null) { + return ZKSecurityTool.convertTablePermissions(serializedPerms).contains(permission); + } + return false; + } + + @Override + public boolean hasNamespacePermission(String user, String namespace, NamespacePermission permission) throws NamespaceNotFoundException { + byte[] serializedPerms; + final ZooReaderWriter zrw = ZooReaderWriter.getInstance(); + try { + String path = ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace; + zrw.sync(path); + serializedPerms = zrw.getData(path, null); + } catch (KeeperException e) { + if (e.code() == Code.NONODE) { + // maybe the namespace was just deleted? + try { + // check for existence: + zrw.getData(ZKNamespacePath + "/" + namespace, null); + // it's there, you don't have permission + return false; + } catch (InterruptedException ex) { + log.warn("Unhandled InterruptedException, failing closed for namespace permission check", e); + return false; + } catch (KeeperException ex) { + // not there, throw an informative exception + if (e.code() == Code.NONODE) { + throw new NamespaceNotFoundException(null, namespace, "while checking permissions"); + } + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + } + return false; + } + log.warn("Unhandled KeeperException, failing closed for table permission check", e); + return false; + } catch (InterruptedException e) { + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + return false; + } + if (serializedPerms != null) { + return ZKSecurityTool.convertNamespacePermissions(serializedPerms).contains(permission); + } + return false; + } + + @Override + public boolean hasCachedNamespacePermission(String user, String namespace, NamespacePermission permission) throws AccumuloSecurityException, + NamespaceNotFoundException { + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace); + if (serializedPerms != null) { + return ZKSecurityTool.convertNamespacePermissions(serializedPerms).contains(permission); + } + return false; + } + + @Override + public void grantSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException { + try { + byte[] permBytes = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms); + Set<SystemPermission> perms; + if (permBytes == null) { + perms = new TreeSet<SystemPermission>(); + } else { + perms = ZKSecurityTool.convertSystemPermissions(permBytes); + } + + if (perms.add(permission)) { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(perms), + NodeExistsPolicy.OVERWRITE); + } + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void grantTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException { + Set<TablePermission> tablePerms; + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table); + if (serializedPerms != null) + tablePerms = ZKSecurityTool.convertTablePermissions(serializedPerms); + else + tablePerms = new TreeSet<TablePermission>(); + + try { + if (tablePerms.add(permission)) { + synchronized (zooCache) { + zooCache.clear(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, + ZKSecurityTool.convertTablePermissions(tablePerms), + NodeExistsPolicy.OVERWRITE); + } + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void grantNamespacePermission(String user, String namespace, NamespacePermission permission) throws AccumuloSecurityException { + Set<NamespacePermission> namespacePerms; + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace); + if (serializedPerms != null) + namespacePerms = ZKSecurityTool.convertNamespacePermissions(serializedPerms); + else + namespacePerms = new TreeSet<NamespacePermission>(); + + try { + if (namespacePerms.add(permission)) { + synchronized (zooCache) { + zooCache.clear(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace, + ZKSecurityTool.convertNamespacePermissions(namespacePerms), + NodeExistsPolicy.OVERWRITE); + } + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void revokeSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException { + byte[] sysPermBytes = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms); + + // User had no system permission, nothing to revoke. + if (sysPermBytes == null) + return; + + Set<SystemPermission> sysPerms = ZKSecurityTool.convertSystemPermissions(sysPermBytes); + + try { + if (sysPerms.remove(permission)) { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(sysPerms), + NodeExistsPolicy.OVERWRITE); + } + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void revokeTablePermission(String user, String table, TablePermission permission) throws AccumuloSecurityException { + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table); + + // User had no table permission, nothing to revoke. + if (serializedPerms == null) + return; + + Set<TablePermission> tablePerms = ZKSecurityTool.convertTablePermissions(serializedPerms); + try { + if (tablePerms.remove(permission)) { + zooCache.clear(); + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + if (tablePerms.size() == 0) + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, NodeMissingPolicy.SKIP); + else + zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, ZKSecurityTool.convertTablePermissions(tablePerms), + NodeExistsPolicy.OVERWRITE); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void revokeNamespacePermission(String user, String namespace, NamespacePermission permission) throws AccumuloSecurityException { + byte[] serializedPerms = zooCache.get(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace); + + // User had no namespace permission, nothing to revoke. + if (serializedPerms == null) + return; + + Set<NamespacePermission> namespacePerms = ZKSecurityTool.convertNamespacePermissions(serializedPerms); + try { + if (namespacePerms.remove(permission)) { + zooCache.clear(); + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + if (namespacePerms.size() == 0) + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace, NodeMissingPolicy.SKIP); + else + zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace, ZKSecurityTool.convertNamespacePermissions(namespacePerms), + NodeExistsPolicy.OVERWRITE); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void cleanTablePermissions(String table) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + zooCache.clear(); + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + for (String user : zooCache.getChildren(ZKUserPath)) + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, NodeMissingPolicy.SKIP); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException("unknownUser", SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void cleanNamespacePermissions(String namespace) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + zooCache.clear(); + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + for (String user : zooCache.getChildren(ZKUserPath)) + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace, NodeMissingPolicy.SKIP); + } + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException("unknownUser", SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void initializeSecurity(TCredentials itw, String rootuser) throws AccumuloSecurityException { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + + // create the root user with all system privileges, no table privileges, and no record-level authorizations + Set<SystemPermission> rootPerms = new TreeSet<SystemPermission>(); + for (SystemPermission p : SystemPermission.values()) + rootPerms.add(p); + Map<String,Set<TablePermission>> tablePerms = new HashMap<String,Set<TablePermission>>(); + // Allow the root user to flush the system tables + tablePerms.put(RootTable.ID, Collections.singleton(TablePermission.ALTER_TABLE)); + tablePerms.put(MetadataTable.ID, Collections.singleton(TablePermission.ALTER_TABLE)); + // essentially the same but on the system namespace, the ALTER_TABLE permission is now redundant + Map<String,Set<NamespacePermission>> namespacePerms = new HashMap<String,Set<NamespacePermission>>(); + namespacePerms.put(Namespaces.ACCUMULO_NAMESPACE_ID, Collections.singleton(NamespacePermission.ALTER_NAMESPACE)); + namespacePerms.put(Namespaces.ACCUMULO_NAMESPACE_ID, Collections.singleton(NamespacePermission.ALTER_TABLE)); + + try { + // prep parent node of users with root username + if (!zoo.exists(ZKUserPath)) - zoo.putPersistentData(ZKUserPath, rootuser.getBytes(Constants.UTF8), NodeExistsPolicy.FAIL); ++ zoo.putPersistentData(ZKUserPath, rootuser.getBytes(UTF_8), NodeExistsPolicy.FAIL); + + initUser(rootuser); + zoo.putPersistentData(ZKUserPath + "/" + rootuser + ZKUserSysPerms, ZKSecurityTool.convertSystemPermissions(rootPerms), NodeExistsPolicy.FAIL); + for (Entry<String,Set<TablePermission>> entry : tablePerms.entrySet()) + createTablePerm(rootuser, entry.getKey(), entry.getValue()); + for (Entry<String,Set<NamespacePermission>> entry : namespacePerms.entrySet()) + createNamespacePerm(rootuser, entry.getKey(), entry.getValue()); + } catch (KeeperException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + @Override + public void initUser(String user) throws AccumuloSecurityException { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + try { + zoo.putPersistentData(ZKUserPath + "/" + user, new byte[0], NodeExistsPolicy.SKIP); + zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms, new byte[0], NodeExistsPolicy.SKIP); + zoo.putPersistentData(ZKUserPath + "/" + user + ZKUserNamespacePerms, new byte[0], NodeExistsPolicy.SKIP); + } catch (KeeperException e) { + log.error(e, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } + } + + /** + * Sets up a new table configuration for the provided user/table. No checking for existence is done here, it should be done before calling. + */ + private void createTablePerm(String user, String table, Set<TablePermission> perms) throws KeeperException, InterruptedException { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserTablePerms + "/" + table, + ZKSecurityTool.convertTablePermissions(perms), NodeExistsPolicy.FAIL); + } + } + + /** + * Sets up a new namespace configuration for the provided user/table. No checking for existence is done here, it should be done before calling. + */ + private void createNamespacePerm(String user, String namespace, Set<NamespacePermission> perms) throws KeeperException, InterruptedException { + synchronized (zooCache) { + zooCache.clear(); + ZooReaderWriter.getInstance().putPersistentData(ZKUserPath + "/" + user + ZKUserNamespacePerms + "/" + namespace, + ZKSecurityTool.convertNamespacePermissions(perms), NodeExistsPolicy.FAIL); + } + } + + @Override + public void cleanUser(String user) throws AccumuloSecurityException { + try { + synchronized (zooCache) { + IZooReaderWriter zoo = ZooReaderWriter.getInstance(); + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserSysPerms, NodeMissingPolicy.SKIP); + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserTablePerms, NodeMissingPolicy.SKIP); + zoo.recursiveDelete(ZKUserPath + "/" + user + ZKUserNamespacePerms, NodeMissingPolicy.SKIP); + zooCache.clear(ZKUserPath + "/" + user); + } + } catch (InterruptedException e) { + log.error(e, e); + throw new RuntimeException(e); + } catch (KeeperException e) { + log.error(e, e); + if (e.code().equals(KeeperException.Code.NONODE)) + throw new AccumuloSecurityException(user, SecurityErrorCode.USER_DOESNT_EXIST, e); + throw new AccumuloSecurityException(user, SecurityErrorCode.CONNECTION_ERROR, e); + + } + } + + @Override + public boolean hasSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException { + byte[] perms; + try { + String path = ZKUserPath + "/" + user + ZKUserSysPerms; + ZooReaderWriter.getInstance().sync(path); + perms = ZooReaderWriter.getInstance().getData(path, null); + } catch (KeeperException e) { + if (e.code() == Code.NONODE) { + return false; + } + log.warn("Unhandled KeeperException, failing closed for table permission check", e); + return false; + } catch (InterruptedException e) { + log.warn("Unhandled InterruptedException, failing closed for table permission check", e); + return false; + } + + if (perms == null) + return false; + return ZKSecurityTool.convertSystemPermissions(perms).contains(permission); + } + + @Override + public boolean hasCachedSystemPermission(String user, SystemPermission permission) throws AccumuloSecurityException { + byte[] perms = zooCache.get(ZKUserPath + "/" + user + ZKUserSysPerms); + if (perms == null) + return false; + return ZKSecurityTool.convertSystemPermissions(perms).contains(permission); + } + + @Override + public boolean validSecurityHandlers(Authenticator authent, Authorizor author) { + return true; + } + + @Override + public void initTable(String table) throws AccumuloSecurityException { + // All proper housekeeping is done on delete and permission granting, no work needs to be done here + } +}