/* * 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.client; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; 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.Instance; import org.apache.accumulo.core.client.NamespaceNotFoundException; import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.impl.Credentials; import org.apache.accumulo.core.client.impl.Namespaces; import org.apache.accumulo.core.client.impl.Tables; import org.apache.accumulo.core.client.impl.thrift.ClientService; import org.apache.accumulo.core.client.impl.thrift.ConfigurationType; import org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode; import org.apache.accumulo.core.client.impl.thrift.TDiskUsage; import org.apache.accumulo.core.client.impl.thrift.TableOperation; import org.apache.accumulo.core.client.impl.thrift.TableOperationExceptionType; import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException; import org.apache.accumulo.core.client.impl.thrift.ThriftTableOperationException; import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; import org.apache.accumulo.core.client.security.tokens.KerberosToken; import org.apache.accumulo.core.client.security.tokens.PasswordToken; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.master.thrift.BulkImportState; import org.apache.accumulo.core.master.thrift.BulkImportStatus; import org.apache.accumulo.core.security.Authorizations; 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.core.trace.thrift.TInfo; import org.apache.accumulo.server.AccumuloServerContext; import org.apache.accumulo.server.conf.ServerConfigurationFactory; import org.apache.accumulo.server.fs.VolumeManager; import org.apache.accumulo.server.security.AuditedSecurityOperation; import org.apache.accumulo.server.security.SecurityOperation; import org.apache.accumulo.server.util.ServerBulkImportStatus; import org.apache.accumulo.server.util.TableDiskUsage; import org.apache.accumulo.server.zookeeper.TransactionWatcher; import org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ClientServiceHandler implements ClientService.Iface { private static final Logger log = LoggerFactory.getLogger(ClientServiceHandler.class); protected final TransactionWatcher transactionWatcher; private final AccumuloServerContext context; private final Instance instance; private final VolumeManager fs; private final SecurityOperation security; private final ServerBulkImportStatus bulkImportStatus = new ServerBulkImportStatus(); public ClientServiceHandler(AccumuloServerContext context, TransactionWatcher transactionWatcher, VolumeManager fs) { this.context = context; this.instance = context.getInstance(); this.transactionWatcher = transactionWatcher; this.fs = fs; this.security = AuditedSecurityOperation.getInstance(context); } public static String checkTableId(Instance instance, String tableName, TableOperation operation) throws ThriftTableOperationException { TableOperationExceptionType reason = null; try { return Tables._getTableId(instance, tableName); } catch (NamespaceNotFoundException e) { reason = TableOperationExceptionType.NAMESPACE_NOTFOUND; } catch (TableNotFoundException e) { reason = TableOperationExceptionType.NOTFOUND; } throw new ThriftTableOperationException(null, tableName, operation, reason, null); } public static String checkNamespaceId(Instance instance, String namespace, TableOperation operation) throws ThriftTableOperationException { String namespaceId = Namespaces.getNameToIdMap(instance).get(namespace); if (namespaceId == null) { // maybe the namespace exists, but the cache was not updated yet... so try to clear the cache and check again Tables.clearCache(instance); namespaceId = Namespaces.getNameToIdMap(instance).get(namespace); if (namespaceId == null) throw new ThriftTableOperationException(null, namespace, operation, TableOperationExceptionType.NAMESPACE_NOTFOUND, null); } return namespaceId; } @Override public String getInstanceId() { return instance.getInstanceID(); } @Override public String getRootTabletLocation() { return instance.getRootTabletLocation(); } @Override public String getZooKeepers() { return instance.getZooKeepers(); } @Override public void ping(TCredentials credentials) { // anybody can call this; no authentication check log.info("Master reports: I just got pinged!"); } @Override public boolean authenticate(TInfo tinfo, TCredentials credentials) throws ThriftSecurityException { try { return security.authenticateUser(credentials, credentials); } catch (ThriftSecurityException e) { log.error("ThriftSecurityException", e); throw e; } } @Override public boolean authenticateUser(TInfo tinfo, TCredentials credentials, TCredentials toAuth) throws ThriftSecurityException { try { return security.authenticateUser(credentials, toAuth); } catch (ThriftSecurityException e) { log.error("ThriftSecurityException", e); throw e; } } @Override public void changeAuthorizations(TInfo tinfo, TCredentials credentials, String user, List<ByteBuffer> authorizations) throws ThriftSecurityException { security.changeAuthorizations(credentials, user, new Authorizations(authorizations)); } @Override public void changeLocalUserPassword(TInfo tinfo, TCredentials credentials, String principal, ByteBuffer password) throws ThriftSecurityException { PasswordToken token = new PasswordToken(password); Credentials toChange = new Credentials(principal, token); security.changePassword(credentials, toChange); } @Override public void createLocalUser(TInfo tinfo, TCredentials credentials, String principal, ByteBuffer password) throws ThriftSecurityException { AuthenticationToken token; if (null != context.getSaslParams()) { try { token = new KerberosToken(); } catch (IOException e) { log.warn("Failed to create KerberosToken"); throw new ThriftSecurityException(e.getMessage(), SecurityErrorCode.DEFAULT_SECURITY_ERROR); } } else { token = new PasswordToken(password); } Credentials newUser = new Credentials(principal, token); security.createUser(credentials, newUser, new Authorizations()); } @Override public void dropLocalUser(TInfo tinfo, TCredentials credentials, String user) throws ThriftSecurityException { security.dropUser(credentials, user); } @Override public List<ByteBuffer> getUserAuthorizations(TInfo tinfo, TCredentials credentials, String user) throws ThriftSecurityException { return security.getUserAuthorizations(credentials, user).getAuthorizationsBB(); } @Override public void grantSystemPermission(TInfo tinfo, TCredentials credentials, String user, byte permission) throws ThriftSecurityException { security.grantSystemPermission(credentials, user, SystemPermission.getPermissionById(permission)); } @Override public void grantTablePermission(TInfo tinfo, TCredentials credentials, String user, String tableName, byte permission) throws TException { String tableId = checkTableId(instance, tableName, TableOperation.PERMISSION); String namespaceId; try { namespaceId = Tables.getNamespaceId(instance, tableId); } catch (TableNotFoundException e) { throw new TException(e); } security.grantTablePermission(credentials, user, tableId, TablePermission.getPermissionById(permission), namespaceId); } @Override public void grantNamespacePermission(TInfo tinfo, TCredentials credentials, String user, String ns, byte permission) throws ThriftSecurityException, ThriftTableOperationException { String namespaceId = checkNamespaceId(instance, ns, TableOperation.PERMISSION); security.grantNamespacePermission(credentials, user, namespaceId, NamespacePermission.getPermissionById(permission)); } @Override public void revokeSystemPermission(TInfo tinfo, TCredentials credentials, String user, byte permission) throws ThriftSecurityException { security.revokeSystemPermission(credentials, user, SystemPermission.getPermissionById(permission)); } @Override public void revokeTablePermission(TInfo tinfo, TCredentials credentials, String user, String tableName, byte permission) throws TException { String tableId = checkTableId(instance, tableName, TableOperation.PERMISSION); String namespaceId; try { namespaceId = Tables.getNamespaceId(instance, tableId); } catch (TableNotFoundException e) { throw new TException(e); } security.revokeTablePermission(credentials, user, tableId, TablePermission.getPermissionById(permission), namespaceId); } @Override public boolean hasSystemPermission(TInfo tinfo, TCredentials credentials, String user, byte sysPerm) throws ThriftSecurityException { return security.hasSystemPermission(credentials, user, SystemPermission.getPermissionById(sysPerm)); } @Override public boolean hasTablePermission(TInfo tinfo, TCredentials credentials, String user, String tableName, byte tblPerm) throws ThriftSecurityException, ThriftTableOperationException { String tableId = checkTableId(instance, tableName, TableOperation.PERMISSION); return security.hasTablePermission(credentials, user, tableId, TablePermission.getPermissionById(tblPerm)); } @Override public boolean hasNamespacePermission(TInfo tinfo, TCredentials credentials, String user, String ns, byte perm) throws ThriftSecurityException, ThriftTableOperationException { String namespaceId = checkNamespaceId(instance, ns, TableOperation.PERMISSION); return security.hasNamespacePermission(credentials, user, namespaceId, NamespacePermission.getPermissionById(perm)); } @Override public void revokeNamespacePermission(TInfo tinfo, TCredentials credentials, String user, String ns, byte permission) throws ThriftSecurityException, ThriftTableOperationException { String namespaceId = checkNamespaceId(instance, ns, TableOperation.PERMISSION); security.revokeNamespacePermission(credentials, user, namespaceId, NamespacePermission.getPermissionById(permission)); } @Override public Set<String> listLocalUsers(TInfo tinfo, TCredentials credentials) throws ThriftSecurityException { return security.listUsers(credentials); } private Map<String,String> conf(TCredentials credentials, AccumuloConfiguration conf) throws TException { security.authenticateUser(credentials, credentials); conf.invalidateCache(); Map<String,String> result = new HashMap<>(); for (Entry<String,String> entry : conf) { String key = entry.getKey(); if (!Property.isSensitive(key)) result.put(key, entry.getValue()); } return result; } @Override public Map<String,String> getConfiguration(TInfo tinfo, TCredentials credentials, ConfigurationType type) throws TException { ServerConfigurationFactory factory = context.getServerConfigurationFactory(); switch (type) { case CURRENT: return conf(credentials, factory.getSystemConfiguration()); case SITE: return conf(credentials, factory.getSiteConfiguration()); case DEFAULT: return conf(credentials, factory.getDefaultConfiguration()); } throw new RuntimeException("Unexpected configuration type " + type); } @Override public Map<String,String> getTableConfiguration(TInfo tinfo, TCredentials credentials, String tableName) throws TException, ThriftTableOperationException { String tableId = checkTableId(instance, tableName, null); AccumuloConfiguration config = context.getServerConfigurationFactory().getTableConfiguration(tableId); return conf(credentials, config); } @Override public List<String> bulkImportFiles(TInfo tinfo, final TCredentials credentials, final long tid, final String tableId, final List<String> files, final String errorDir, final boolean setTime) throws ThriftSecurityException, ThriftTableOperationException, TException { try { if (!security.canPerformSystemActions(credentials)) throw new AccumuloSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); bulkImportStatus.updateBulkImportStatus(files, BulkImportState.INITIAL); log.debug("Got request to bulk import files to table(" + tableId + "): " + files); return transactionWatcher.run(Constants.BULK_ARBITRATOR_TYPE, tid, new Callable<List<String>>() { @Override public List<String> call() throws Exception { bulkImportStatus.updateBulkImportStatus(files, BulkImportState.PROCESSING); try { return BulkImporter.bulkLoad(context, tid, tableId, files, errorDir, setTime); } finally { bulkImportStatus.removeBulkImportStatus(files); } } }); } catch (AccumuloSecurityException e) { throw e.asThriftException(); } catch (Exception ex) { throw new TException(ex); } } @Override public boolean isActive(TInfo tinfo, long tid) throws TException { return transactionWatcher.isActive(tid); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public boolean checkClass(TInfo tinfo, TCredentials credentials, String className, String interfaceMatch) throws TException { security.authenticateUser(credentials, credentials); ClassLoader loader = getClass().getClassLoader(); Class shouldMatch; try { shouldMatch = loader.loadClass(interfaceMatch); Class test = AccumuloVFSClassLoader.loadClass(className, shouldMatch); test.newInstance(); return true; } catch (ClassCastException e) { log.warn("Error checking object types", e); return false; } catch (ClassNotFoundException e) { log.warn("Error checking object types", e); return false; } catch (InstantiationException e) { log.warn("Error checking object types", e); return false; } catch (IllegalAccessException e) { log.warn("Error checking object types", e); return false; } } @Override public boolean checkTableClass(TInfo tinfo, TCredentials credentials, String tableName, String className, String interfaceMatch) throws TException, ThriftTableOperationException, ThriftSecurityException { security.authenticateUser(credentials, credentials); String tableId = checkTableId(instance, tableName, null); ClassLoader loader = getClass().getClassLoader(); Class<?> shouldMatch; try { shouldMatch = loader.loadClass(interfaceMatch); AccumuloConfiguration conf = context.getServerConfigurationFactory().getTableConfiguration(tableId); String context = conf.get(Property.TABLE_CLASSPATH); ClassLoader currentLoader; if (context != null && !context.equals("")) { currentLoader = AccumuloVFSClassLoader.getContextManager().getClassLoader(context); } else { currentLoader = AccumuloVFSClassLoader.getClassLoader(); } Class<?> test = currentLoader.loadClass(className).asSubclass(shouldMatch); test.newInstance(); return true; } catch (Exception e) { log.warn("Error checking object types", e); return false; } } @Override public boolean checkNamespaceClass(TInfo tinfo, TCredentials credentials, String ns, String className, String interfaceMatch) throws TException, ThriftTableOperationException, ThriftSecurityException { security.authenticateUser(credentials, credentials); String namespaceId = checkNamespaceId(instance, ns, null); ClassLoader loader = getClass().getClassLoader(); Class<?> shouldMatch; try { shouldMatch = loader.loadClass(interfaceMatch); AccumuloConfiguration conf = context.getServerConfigurationFactory().getNamespaceConfiguration(namespaceId); String context = conf.get(Property.TABLE_CLASSPATH); ClassLoader currentLoader; if (context != null && !context.equals("")) { currentLoader = AccumuloVFSClassLoader.getContextManager().getClassLoader(context); } else { currentLoader = AccumuloVFSClassLoader.getClassLoader(); } Class<?> test = currentLoader.loadClass(className).asSubclass(shouldMatch); test.newInstance(); return true; } catch (Exception e) { log.warn("Error checking object types", e); return false; } } @Override public List<TDiskUsage> getDiskUsage(Set<String> tables, TCredentials credentials) throws ThriftTableOperationException, ThriftSecurityException, TException { try { HashSet<String> tableIds = new HashSet<>(); for (String table : tables) { // ensure that table table exists String tableId = checkTableId(instance, table, null); tableIds.add(tableId); String namespaceId = Tables.getNamespaceId(instance, tableId); if (!security.canScan(credentials, tableId, namespaceId)) throw new ThriftSecurityException(credentials.getPrincipal(), SecurityErrorCode.PERMISSION_DENIED); } // use the same set of tableIds that were validated above to avoid race conditions Map<TreeSet<String>,Long> diskUsage = TableDiskUsage.getDiskUsage(context.getServerConfigurationFactory().getSystemConfiguration(), tableIds, fs, context.getConnector()); List<TDiskUsage> retUsages = new ArrayList<>(); for (Map.Entry<TreeSet<String>,Long> usageItem : diskUsage.entrySet()) { retUsages.add(new TDiskUsage(new ArrayList<>(usageItem.getKey()), usageItem.getValue())); } return retUsages; } catch (AccumuloSecurityException e) { throw e.asThriftException(); } catch (AccumuloException e) { throw new TException(e); } catch (IOException e) { throw new TException(e); } catch (TableNotFoundException e) { throw new TException(e); } } @Override public Map<String,String> getNamespaceConfiguration(TInfo tinfo, TCredentials credentials, String ns) throws ThriftTableOperationException, TException { String namespaceId; try { namespaceId = Namespaces.getNamespaceId(instance, ns); } catch (NamespaceNotFoundException e) { String why = "Could not find namespace while getting configuration."; throw new ThriftTableOperationException(null, ns, null, TableOperationExceptionType.NAMESPACE_NOTFOUND, why); } AccumuloConfiguration config = context.getServerConfigurationFactory().getNamespaceConfiguration(namespaceId); return conf(credentials, config); } public List<BulkImportStatus> getBulkLoadStatus() { return bulkImportStatus.getBulkLoadStatus(); } }