/* * Licensed 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 com.facebook.presto.security; import com.facebook.presto.connector.ConnectorId; import com.facebook.presto.metadata.QualifiedObjectName; import com.facebook.presto.spi.CatalogSchemaName; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.spi.connector.ConnectorAccessControl; import com.facebook.presto.spi.connector.ConnectorTransactionHandle; import com.facebook.presto.spi.security.Identity; import com.facebook.presto.spi.security.Privilege; import com.facebook.presto.spi.security.SystemAccessControl; import com.facebook.presto.spi.security.SystemAccessControlFactory; import com.facebook.presto.transaction.TransactionId; import com.facebook.presto.transaction.TransactionManager; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.airlift.log.Logger; import io.airlift.stats.CounterStat; import org.weakref.jmx.Managed; import org.weakref.jmx.Nested; import javax.inject.Inject; import java.io.File; import java.io.FileInputStream; import java.security.Principal; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static com.facebook.presto.spi.StandardErrorCode.SERVER_STARTING_UP; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Maps.fromProperties; import static java.lang.String.format; import static java.util.Objects.requireNonNull; public class AccessControlManager implements AccessControl { private static final Logger log = Logger.get(AccessControlManager.class); private static final File ACCESS_CONTROL_CONFIGURATION = new File("etc/access-control.properties"); private static final String ACCESS_CONTROL_PROPERTY_NAME = "access-control.name"; private final TransactionManager transactionManager; private final Map<String, SystemAccessControlFactory> systemAccessControlFactories = new ConcurrentHashMap<>(); private final Map<ConnectorId, CatalogAccessControlEntry> connectorAccessControl = new ConcurrentHashMap<>(); private final AtomicReference<SystemAccessControl> systemAccessControl = new AtomicReference<>(new InitializingSystemAccessControl()); private final AtomicBoolean systemAccessControlLoading = new AtomicBoolean(); private final CounterStat authenticationSuccess = new CounterStat(); private final CounterStat authenticationFail = new CounterStat(); private final CounterStat authorizationSuccess = new CounterStat(); private final CounterStat authorizationFail = new CounterStat(); @Inject public AccessControlManager(TransactionManager transactionManager) { this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); addSystemAccessControlFactory(new AllowAllSystemAccessControl.Factory()); addSystemAccessControlFactory(new ReadOnlySystemAccessControl.Factory()); } public void addSystemAccessControlFactory(SystemAccessControlFactory accessControlFactory) { requireNonNull(accessControlFactory, "accessControlFactory is null"); if (systemAccessControlFactories.putIfAbsent(accessControlFactory.getName(), accessControlFactory) != null) { throw new IllegalArgumentException(format("Access control '%s' is already registered", accessControlFactory.getName())); } } public void addCatalogAccessControl(ConnectorId connectorId, ConnectorAccessControl accessControl) { requireNonNull(connectorId, "connectorId is null"); requireNonNull(accessControl, "accessControl is null"); checkState(connectorAccessControl.putIfAbsent(connectorId, new CatalogAccessControlEntry(connectorId, accessControl)) == null, "Access control for connector '%s' is already registered", connectorId); } public void removeCatalogAccessControl(ConnectorId connectorId) { connectorAccessControl.remove(connectorId); } public void loadSystemAccessControl() throws Exception { if (ACCESS_CONTROL_CONFIGURATION.exists()) { Map<String, String> properties = new HashMap<>(loadProperties(ACCESS_CONTROL_CONFIGURATION)); String accessControlName = properties.remove(ACCESS_CONTROL_PROPERTY_NAME); checkArgument(!isNullOrEmpty(accessControlName), "Access control configuration %s does not contain %s", ACCESS_CONTROL_CONFIGURATION.getAbsoluteFile(), ACCESS_CONTROL_PROPERTY_NAME); setSystemAccessControl(accessControlName, properties); } else { setSystemAccessControl(AllowAllSystemAccessControl.NAME, ImmutableMap.of()); } } @VisibleForTesting protected void setSystemAccessControl(String name, Map<String, String> properties) { requireNonNull(name, "name is null"); requireNonNull(properties, "properties is null"); checkState(systemAccessControlLoading.compareAndSet(false, true), "System access control already initialized"); log.info("-- Loading system access control --"); SystemAccessControlFactory systemAccessControlFactory = systemAccessControlFactories.get(name); checkState(systemAccessControlFactory != null, "Access control %s is not registered", name); SystemAccessControl systemAccessControl = systemAccessControlFactory.create(ImmutableMap.copyOf(properties)); this.systemAccessControl.set(systemAccessControl); log.info("-- Loaded system access control %s --", name); } @Override public void checkCanSetUser(Principal principal, String userName) { requireNonNull(userName, "userName is null"); authenticationCheck(() -> systemAccessControl.get().checkCanSetUser(principal, userName)); } @Override public Set<String> filterCatalogs(Identity identity, Set<String> catalogs) { requireNonNull(identity, "identity is null"); requireNonNull(catalogs, "catalogs is null"); return systemAccessControl.get().filterCatalogs(identity, catalogs); } @Override public void checkCanCreateSchema(TransactionId transactionId, Identity identity, CatalogSchemaName schemaName) { requireNonNull(identity, "identity is null"); requireNonNull(schemaName, "schemaName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanCreateSchema(identity, schemaName)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, schemaName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanCreateSchema(entry.getTransactionHandle(transactionId), identity, schemaName.getSchemaName())); } } @Override public void checkCanDropSchema(TransactionId transactionId, Identity identity, CatalogSchemaName schemaName) { requireNonNull(identity, "identity is null"); requireNonNull(schemaName, "schemaName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanDropSchema(identity, schemaName)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, schemaName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanDropSchema(entry.getTransactionHandle(transactionId), identity, schemaName.getSchemaName())); } } @Override public void checkCanRenameSchema(TransactionId transactionId, Identity identity, CatalogSchemaName schemaName, String newSchemaName) { requireNonNull(identity, "identity is null"); requireNonNull(schemaName, "schemaName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanRenameSchema(identity, schemaName, newSchemaName)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, schemaName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanRenameSchema(entry.getTransactionHandle(transactionId), identity, schemaName.getSchemaName(), newSchemaName)); } } @Override public void checkCanShowSchemas(TransactionId transactionId, Identity identity, String catalogName) { requireNonNull(identity, "identity is null"); requireNonNull(catalogName, "catalogName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanShowSchemas(identity, catalogName)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, catalogName); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanShowSchemas(entry.getTransactionHandle(transactionId), identity)); } } @Override public Set<String> filterSchemas(TransactionId transactionId, Identity identity, String catalogName, Set<String> schemaNames) { requireNonNull(identity, "identity is null"); requireNonNull(catalogName, "catalogName is null"); requireNonNull(schemaNames, "schemaNames is null"); schemaNames = systemAccessControl.get().filterSchemas(identity, catalogName, schemaNames); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, catalogName); if (entry != null) { schemaNames = entry.getAccessControl().filterSchemas(entry.getTransactionHandle(transactionId), identity, schemaNames); } return schemaNames; } @Override public void checkCanCreateTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanCreateTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanCreateTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanDropTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanDropTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanDropTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanRenameTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName, QualifiedObjectName newTableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); requireNonNull(newTableName, "newTableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanRenameTable(identity, tableName.asCatalogSchemaTableName(), newTableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanRenameTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName(), newTableName.asSchemaTableName())); } } @Override public void checkCanShowTablesMetadata(TransactionId transactionId, Identity identity, CatalogSchemaName schema) { requireNonNull(identity, "identity is null"); requireNonNull(schema, "schema is null"); authorizationCheck(() -> systemAccessControl.get().checkCanShowTablesMetadata(identity, schema)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, schema.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanShowTablesMetadata(entry.getTransactionHandle(transactionId), identity, schema.getSchemaName())); } } @Override public Set<SchemaTableName> filterTables(TransactionId transactionId, Identity identity, String catalogName, Set<SchemaTableName> tableNames) { requireNonNull(identity, "identity is null"); requireNonNull(catalogName, "catalogName is null"); requireNonNull(tableNames, "tableNames is null"); tableNames = systemAccessControl.get().filterTables(identity, catalogName, tableNames); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, catalogName); if (entry != null) { tableNames = entry.getAccessControl().filterTables(entry.getTransactionHandle(transactionId), identity, tableNames); } return tableNames; } @Override public void checkCanAddColumns(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanAddColumn(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanAddColumn(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanRenameColumn(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanRenameColumn(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanRenameColumn(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanSelectFromTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanSelectFromTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanSelectFromTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanInsertIntoTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanInsertIntoTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanInsertIntoTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanDeleteFromTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanDeleteFromTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanDeleteFromTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanCreateView(TransactionId transactionId, Identity identity, QualifiedObjectName viewName) { requireNonNull(identity, "identity is null"); requireNonNull(viewName, "viewName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanCreateView(identity, viewName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, viewName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanCreateView(entry.getTransactionHandle(transactionId), identity, viewName.asSchemaTableName())); } } @Override public void checkCanDropView(TransactionId transactionId, Identity identity, QualifiedObjectName viewName) { requireNonNull(identity, "identity is null"); requireNonNull(viewName, "viewName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanDropView(identity, viewName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, viewName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanDropView(entry.getTransactionHandle(transactionId), identity, viewName.asSchemaTableName())); } } @Override public void checkCanSelectFromView(TransactionId transactionId, Identity identity, QualifiedObjectName viewName) { requireNonNull(identity, "identity is null"); requireNonNull(viewName, "viewName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanSelectFromView(identity, viewName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, viewName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanSelectFromView(entry.getTransactionHandle(transactionId), identity, viewName.asSchemaTableName())); } } @Override public void checkCanCreateViewWithSelectFromTable(TransactionId transactionId, Identity identity, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanCreateViewWithSelectFromTable(identity, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanCreateViewWithSelectFromTable(entry.getTransactionHandle(transactionId), identity, tableName.asSchemaTableName())); } } @Override public void checkCanCreateViewWithSelectFromView(TransactionId transactionId, Identity identity, QualifiedObjectName viewName) { requireNonNull(identity, "identity is null"); requireNonNull(viewName, "viewName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanCreateViewWithSelectFromView(identity, viewName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, viewName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanCreateViewWithSelectFromView(entry.getTransactionHandle(transactionId), identity, viewName.asSchemaTableName())); } } @Override public void checkCanGrantTablePrivilege(TransactionId transactionId, Identity identity, Privilege privilege, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); requireNonNull(privilege, "privilege is null"); authorizationCheck(() -> systemAccessControl.get().checkCanGrantTablePrivilege(identity, privilege, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanGrantTablePrivilege(entry.getTransactionHandle(transactionId), identity, privilege, tableName.asSchemaTableName())); } } @Override public void checkCanRevokeTablePrivilege(TransactionId transactionId, Identity identity, Privilege privilege, QualifiedObjectName tableName) { requireNonNull(identity, "identity is null"); requireNonNull(tableName, "tableName is null"); requireNonNull(privilege, "privilege is null"); authorizationCheck(() -> systemAccessControl.get().checkCanRevokeTablePrivilege(identity, privilege, tableName.asCatalogSchemaTableName())); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanRevokeTablePrivilege(entry.getTransactionHandle(transactionId), identity, privilege, tableName.asSchemaTableName())); } } @Override public void checkCanSetSystemSessionProperty(Identity identity, String propertyName) { requireNonNull(identity, "identity is null"); requireNonNull(propertyName, "propertyName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanSetSystemSessionProperty(identity, propertyName)); } @Override public void checkCanSetCatalogSessionProperty(TransactionId transactionId, Identity identity, String catalogName, String propertyName) { requireNonNull(identity, "identity is null"); requireNonNull(catalogName, "catalogName is null"); requireNonNull(propertyName, "propertyName is null"); authorizationCheck(() -> systemAccessControl.get().checkCanSetCatalogSessionProperty(identity, catalogName, propertyName)); CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, catalogName); if (entry != null) { authorizationCheck(() -> entry.getAccessControl().checkCanSetCatalogSessionProperty(identity, propertyName)); } } private CatalogAccessControlEntry getConnectorAccessControl(TransactionId transactionId, String catalogName) { return transactionManager.getOptionalCatalogMetadata(transactionId, catalogName) .map(metadata -> connectorAccessControl.get(metadata.getConnectorId())) .orElse(null); } @Managed @Nested public CounterStat getAuthenticationSuccess() { return authenticationSuccess; } @Managed @Nested public CounterStat getAuthenticationFail() { return authenticationFail; } @Managed @Nested public CounterStat getAuthorizationSuccess() { return authorizationSuccess; } @Managed @Nested public CounterStat getAuthorizationFail() { return authorizationFail; } private void authenticationCheck(Runnable runnable) { try { runnable.run(); authenticationSuccess.update(1); } catch (PrestoException e) { authenticationFail.update(1); throw e; } } private void authorizationCheck(Runnable runnable) { try { runnable.run(); authorizationSuccess.update(1); } catch (PrestoException e) { authorizationFail.update(1); throw e; } } private static Map<String, String> loadProperties(File file) throws Exception { requireNonNull(file, "file is null"); Properties properties = new Properties(); try (FileInputStream in = new FileInputStream(file)) { properties.load(in); } return fromProperties(properties); } private class CatalogAccessControlEntry { private final ConnectorId connectorId; private final ConnectorAccessControl accessControl; public CatalogAccessControlEntry(ConnectorId connectorId, ConnectorAccessControl accessControl) { this.connectorId = requireNonNull(connectorId, "connectorId is null"); this.accessControl = requireNonNull(accessControl, "accessControl is null"); } public ConnectorId getConnectorId() { return connectorId; } public ConnectorAccessControl getAccessControl() { return accessControl; } public ConnectorTransactionHandle getTransactionHandle(TransactionId transactionId) { return transactionManager.getConnectorTransaction(transactionId, connectorId); } } private static class InitializingSystemAccessControl implements SystemAccessControl { @Override public void checkCanSetUser(Principal principal, String userName) { throw new PrestoException(SERVER_STARTING_UP, "Presto server is still initializing"); } @Override public void checkCanSetSystemSessionProperty(Identity identity, String propertyName) { throw new PrestoException(SERVER_STARTING_UP, "Presto server is still initializing"); } } }