This is an automated email from the ASF dual-hosted git repository. xiangfu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push: new ca629d6 Adding pinot server grpc metadata acl (#8030) ca629d6 is described below commit ca629d6c38ac2458ebc9635273e1e95b50ba89ae Author: Xiang Fu <xiangfu.1...@gmail.com> AuthorDate: Thu Jan 20 18:49:36 2022 -0800 Adding pinot server grpc metadata acl (#8030) --- .../apache/pinot/common/metrics/ServerMeter.java | 1 + .../apache/pinot/core/auth/BasicAuthPrincipal.java | 10 +++ .../apache/pinot/core/transport/QueryServer.java | 7 +- .../pinot/core/transport/grpc/GrpcQueryServer.java | 21 ++++- .../apache/pinot/server/access/AccessControl.java | 5 +- .../pinot/server/access/AccessControlFactory.java | 4 + .../pinot/server/access/AllowAllAccessFactory.java | 3 +- .../server/access/BasicAuthAccessFactory.java | 89 ++++++++++++++++++++++ ...cessFactory.java => GrpcRequesterIdentity.java} | 33 ++++---- ...cessFactory.java => HttpRequesterIdentity.java} | 43 +++++++---- ...sControlFactory.java => RequesterIdentity.java} | 10 +-- .../CertBasedTlsChannelAccessControlFactory.java | 4 +- .../pinot/server/api/resources/TablesResource.java | 5 +- .../pinot/server/starter/ServerInstance.java | 11 ++- .../apache/pinot/server/api/AccessControlTest.java | 85 +++++++++++++++++++-- .../apache/pinot/spi/utils/CommonConstants.java | 1 + 16 files changed, 267 insertions(+), 65 deletions(-) diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java index bf4d507..b757728 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/metrics/ServerMeter.java @@ -64,6 +64,7 @@ public enum ServerMeter implements AbstractMetrics.Meter { UNTAR_FAILURES("segments", false), SEGMENT_DOWNLOAD_FAILURES("segments", false), NUM_RESIZES("numResizes", false), + NO_TABLE_ACCESS("tables", true), // Netty connection metrics NETTY_CONNECTION_BYTES_RECEIVED("nettyConnection", true), diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java index e2d7cb9..20831a0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthPrincipal.java @@ -53,4 +53,14 @@ public class BasicAuthPrincipal { public boolean hasPermission(String permission) { return _permissions.isEmpty() || _permissions.contains(permission.toLowerCase()); } + + @Override + public String toString() { + return "BasicAuthPrincipal{" + + "_name='" + _name + '\'' + + ", _token='" + _token + '\'' + + ", _tables=" + _tables + + ", _permissions=" + _permissions + + '}'; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java index 2d68393..9a9f22b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java @@ -36,7 +36,6 @@ import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.core.query.scheduler.QueryScheduler; import org.apache.pinot.core.util.TlsUtils; import org.apache.pinot.server.access.AccessControl; -import org.apache.pinot.server.access.AccessControlFactory; import org.apache.pinot.server.access.AllowAllAccessFactory; @@ -63,7 +62,7 @@ public class QueryServer { * @param serverMetrics server metrics */ public QueryServer(int port, QueryScheduler queryScheduler, ServerMetrics serverMetrics) { - this(port, queryScheduler, serverMetrics, null, new AllowAllAccessFactory()); + this(port, queryScheduler, serverMetrics, null, new AllowAllAccessFactory().create()); } /** @@ -76,12 +75,12 @@ public class QueryServer { * @param accessControlFactory access control factory for netty channel */ public QueryServer(int port, QueryScheduler queryScheduler, ServerMetrics serverMetrics, TlsConfig tlsConfig, - AccessControlFactory accessControlFactory) { + AccessControl accessControl) { _port = port; _queryScheduler = queryScheduler; _serverMetrics = serverMetrics; _tlsConfig = tlsConfig; - _accessControl = accessControlFactory.create(); + _accessControl = accessControl; } public void start() { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java index 64a6407..1711ec7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java @@ -35,6 +35,8 @@ import org.apache.pinot.core.operator.streaming.StreamingResponseUtils; import org.apache.pinot.core.query.executor.QueryExecutor; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.server.access.AccessControl; +import org.apache.pinot.server.access.GrpcRequesterIdentity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,11 +50,14 @@ public class GrpcQueryServer extends PinotQueryServerGrpc.PinotQueryServerImplBa private final Server _server; private final ExecutorService _executorService = Executors.newFixedThreadPool(ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + private final AccessControl _accessControl; - public GrpcQueryServer(int port, QueryExecutor queryExecutor, ServerMetrics serverMetrics) { + public GrpcQueryServer(int port, QueryExecutor queryExecutor, ServerMetrics serverMetrics, + AccessControl accessControl) { _queryExecutor = queryExecutor; _serverMetrics = serverMetrics; _server = ServerBuilder.forPort(port).addService(this).build(); + _accessControl = accessControl; LOGGER.info("Initialized GrpcQueryServer on port: {} with numWorkerThreads: {}", port, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); } @@ -88,6 +93,20 @@ public class GrpcQueryServer extends PinotQueryServerGrpc.PinotQueryServerImplBa return; } + // Table level access control + GrpcRequesterIdentity requestIdentity = new GrpcRequesterIdentity(request.getMetadataMap()); + if (!_accessControl.hasDataAccess(requestIdentity, queryRequest.getTableNameWithType())) { + Exception unsupportedOperationException = new UnsupportedOperationException( + String.format("No access to table %s while processing request %d: %s from broker: %s", + queryRequest.getTableNameWithType(), queryRequest.getRequestId(), + queryRequest.getQueryContext(), queryRequest.getBrokerId())); + final String exceptionMsg = String.format("Table not found: %s", queryRequest.getTableNameWithType()); + LOGGER.error(exceptionMsg, unsupportedOperationException); + _serverMetrics.addMeteredGlobalValue(ServerMeter.NO_TABLE_ACCESS, 1); + responseObserver.onError( + Status.NOT_FOUND.withDescription(exceptionMsg).withCause(unsupportedOperationException).asException()); + } + // Process the query DataTable dataTable; try { diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControl.java b/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControl.java index 7c78696..254e239 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControl.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControl.java @@ -19,7 +19,6 @@ package org.apache.pinot.server.access; import io.netty.channel.ChannelHandlerContext; -import javax.ws.rs.core.HttpHeaders; import org.apache.pinot.spi.annotations.InterfaceAudience; import org.apache.pinot.spi.annotations.InterfaceStability; @@ -38,9 +37,9 @@ public interface AccessControl { /** * Return whether the client has data access to the given table. * - * @param httpHeaders Http headers + * @param requesterIdentity Request identity * @param tableName Name of the table to be accessed * @return Whether the client has data access to the table */ - boolean hasDataAccess(HttpHeaders httpHeaders, String tableName); + boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName); } diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java index 60a415c..6253d67 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java @@ -20,11 +20,15 @@ package org.apache.pinot.server.access; import org.apache.pinot.spi.annotations.InterfaceAudience; import org.apache.pinot.spi.annotations.InterfaceStability; +import org.apache.pinot.spi.env.PinotConfiguration; @InterfaceAudience.Public @InterfaceStability.Stable public interface AccessControlFactory { + default void init(PinotConfiguration configuration) { + }; + AccessControl create(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java index 299c37b..4fbe22c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java @@ -19,7 +19,6 @@ package org.apache.pinot.server.access; import io.netty.channel.ChannelHandlerContext; -import javax.ws.rs.core.HttpHeaders; public class AllowAllAccessFactory implements AccessControlFactory { @@ -30,7 +29,7 @@ public class AllowAllAccessFactory implements AccessControlFactory { } @Override - public boolean hasDataAccess(HttpHeaders httpHeaders, String tableName) { + public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { return true; } }; diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java new file mode 100644 index 0000000..3431b5c --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java @@ -0,0 +1,89 @@ +/** + * 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.pinot.server.access; + +import io.netty.channel.ChannelHandlerContext; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; +import org.apache.pinot.core.auth.BasicAuthPrincipal; +import org.apache.pinot.core.auth.BasicAuthUtils; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; + + +public class BasicAuthAccessFactory implements AccessControlFactory { + private static final String PREFIX = "principals"; + + private static final String AUTHORIZATION_KEY = "authorization"; + + private AccessControl _accessControl; + + public void init(PinotConfiguration configuration) { + _accessControl = new BasicAuthAccessControl(BasicAuthUtils.extractBasicAuthPrincipals(configuration, PREFIX)); + } + + public AccessControl create() { + return _accessControl; + } + + /** + * Access Control using metadata-based basic grpc authentication + */ + private static class BasicAuthAccessControl implements AccessControl { + private final Map<String, BasicAuthPrincipal> _token2principal; + + public BasicAuthAccessControl(Collection<BasicAuthPrincipal> principals) { + _token2principal = principals.stream().collect(Collectors.toMap(BasicAuthPrincipal::getToken, p -> p)); + } + + @Override + public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) { + return true; + } + + @Override + public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { + Collection<String> tokens = getTokens(requesterIdentity); + return tokens.stream() + .map(BasicAuthUtils::normalizeBase64Token) + .map(_token2principal::get) + .filter(Objects::nonNull) + .findFirst() + // existence of principal required to allow access + .map(principal -> StringUtils.isEmpty(tableName) || principal.hasTable( + TableNameBuilder.extractRawTableName(tableName))) + .orElse(false); + } + + private Collection<String> getTokens(RequesterIdentity requesterIdentity) { + if (requesterIdentity instanceof GrpcRequesterIdentity) { + GrpcRequesterIdentity identity = (GrpcRequesterIdentity) requesterIdentity; + return identity.getGrpcMetadata().get(AUTHORIZATION_KEY); + } + if (requesterIdentity instanceof HttpRequesterIdentity) { + HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; + return identity.getHttpHeaders().get(AUTHORIZATION_KEY); + } + throw new UnsupportedOperationException("GrpcRequesterIdentity or HttpRequesterIdentity is required"); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/GrpcRequesterIdentity.java similarity index 59% copy from pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java copy to pinot-core/src/main/java/org/apache/pinot/server/access/GrpcRequesterIdentity.java index 299c37b..4d7d558 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/GrpcRequesterIdentity.java @@ -18,25 +18,26 @@ */ package org.apache.pinot.server.access; -import io.netty.channel.ChannelHandlerContext; -import javax.ws.rs.core.HttpHeaders; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import java.util.Map; -public class AllowAllAccessFactory implements AccessControlFactory { - private static final AccessControl ALLOW_ALL_ACCESS = new AccessControl() { - @Override - public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) { - return true; - } +/** + * Identity container for GRPC requests with (optional) authorization metadata + */ +public class GrpcRequesterIdentity extends RequesterIdentity { + private Multimap<String, String> _metaData; - @Override - public boolean hasDataAccess(HttpHeaders httpHeaders, String tableName) { - return true; - } - }; + public GrpcRequesterIdentity(Map<String, String> metadataMap) { + _metaData = Multimaps.forMap(metadataMap); + } + + public Multimap<String, String> getGrpcMetadata() { + return _metaData; + } - @Override - public AccessControl create() { - return ALLOW_ALL_ACCESS; + public void setGrpcMetadata(Multimap<String, String> metaData) { + _metaData = metaData; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/HttpRequesterIdentity.java similarity index 51% copy from pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java copy to pinot-core/src/main/java/org/apache/pinot/server/access/HttpRequesterIdentity.java index 299c37b..2233ccd 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AllowAllAccessFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/HttpRequesterIdentity.java @@ -18,25 +18,36 @@ */ package org.apache.pinot.server.access; -import io.netty.channel.ChannelHandlerContext; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import javax.ws.rs.core.HttpHeaders; -public class AllowAllAccessFactory implements AccessControlFactory { - private static final AccessControl ALLOW_ALL_ACCESS = new AccessControl() { - @Override - public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) { - return true; - } +/** + * Identity container for HTTP requests with (optional) authorization headers + */ +public class HttpRequesterIdentity extends RequesterIdentity { + private Multimap<String, String> _httpHeaders; + private String _endpointUrl; - @Override - public boolean hasDataAccess(HttpHeaders httpHeaders, String tableName) { - return true; - } - }; + public HttpRequesterIdentity(HttpHeaders httpHeaders) { + _httpHeaders = HashMultimap.create(); + httpHeaders.getRequestHeaders().forEach(_httpHeaders::putAll); + } + + public Multimap<String, String> getHttpHeaders() { + return _httpHeaders; + } + + public void setHttpHeaders(Multimap<String, String> httpHeaders) { + _httpHeaders = httpHeaders; + } + + public String getEndpointUrl() { + return _endpointUrl; + } - @Override - public AccessControl create() { - return ALLOW_ALL_ACCESS; + public void setEndpointUrl(String endpointUrl) { + _endpointUrl = endpointUrl; } -} +} \ No newline at end of file diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/RequesterIdentity.java similarity index 77% copy from pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java copy to pinot-core/src/main/java/org/apache/pinot/server/access/RequesterIdentity.java index 60a415c..ae4a75a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/AccessControlFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/RequesterIdentity.java @@ -18,13 +18,5 @@ */ package org.apache.pinot.server.access; -import org.apache.pinot.spi.annotations.InterfaceAudience; -import org.apache.pinot.spi.annotations.InterfaceStability; - - -@InterfaceAudience.Public -@InterfaceStability.Stable -public interface AccessControlFactory { - - AccessControl create(); +public abstract class RequesterIdentity { } diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java index cc9f752..99f9501 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/access/CertBasedTlsChannelAccessControlFactory.java @@ -29,9 +29,9 @@ import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.Set; import javax.net.ssl.SSLPeerUnverifiedException; -import javax.ws.rs.core.HttpHeaders; import org.apache.pinot.server.access.AccessControl; import org.apache.pinot.server.access.AccessControlFactory; +import org.apache.pinot.server.access.RequesterIdentity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +67,7 @@ public class CertBasedTlsChannelAccessControlFactory implements AccessControlFac } @Override - public boolean hasDataAccess(HttpHeaders httpHeaders, String tableName) { + public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { return true; } } diff --git a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java index ebf3346..c4ec829 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java @@ -70,6 +70,8 @@ import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.server.access.AccessControl; import org.apache.pinot.server.access.AccessControlFactory; +import org.apache.pinot.server.access.HttpRequesterIdentity; +import org.apache.pinot.server.access.RequesterIdentity; import org.apache.pinot.server.starter.ServerInstance; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -343,7 +345,8 @@ public class TablesResource { boolean hasDataAccess; try { AccessControl accessControl = _accessControlFactory.create(); - hasDataAccess = accessControl.hasDataAccess(httpHeaders, tableNameWithType); + RequesterIdentity httpRequestIdentity = new HttpRequesterIdentity(httpHeaders); + hasDataAccess = accessControl.hasDataAccess(httpRequestIdentity, tableNameWithType); } catch (Exception e) { throw new WebApplicationException("Caught exception while validating access to table: " + tableNameWithType, Response.Status.INTERNAL_SERVER_ERROR); diff --git a/pinot-server/src/main/java/org/apache/pinot/server/starter/ServerInstance.java b/pinot-server/src/main/java/org/apache/pinot/server/starter/ServerInstance.java index c0f781b..b22403d 100644 --- a/pinot-server/src/main/java/org/apache/pinot/server/starter/ServerInstance.java +++ b/pinot-server/src/main/java/org/apache/pinot/server/starter/ServerInstance.java @@ -36,6 +36,7 @@ import org.apache.pinot.core.transport.QueryServer; import org.apache.pinot.core.transport.TlsConfig; import org.apache.pinot.core.transport.grpc.GrpcQueryServer; import org.apache.pinot.core.util.TlsUtils; +import org.apache.pinot.server.access.AccessControl; import org.apache.pinot.server.access.AccessControlFactory; import org.apache.pinot.server.conf.ServerConf; import org.apache.pinot.spi.env.PinotConfiguration; @@ -60,7 +61,7 @@ public class ServerInstance { private final QueryServer _nettyQueryServer; private final QueryServer _nettyTlsQueryServer; private final GrpcQueryServer _grpcQueryServer; - private final AccessControlFactory _accessControlFactory; + private final AccessControl _accessControl; private boolean _started = false; @@ -95,7 +96,9 @@ public class ServerInstance { TlsConfig tlsConfig = TlsUtils.extractTlsConfig(serverConf.getPinotConfig(), CommonConstants.Server.SERVER_TLS_PREFIX); - _accessControlFactory = accessControlFactory; + accessControlFactory.init( + serverConf.getPinotConfig().subset(CommonConstants.Server.PREFIX_OF_CONFIG_OF_ACCESS_CONTROL)); + _accessControl = accessControlFactory.create(); if (serverConf.isNettyServerEnabled()) { int nettyPort = serverConf.getNettyPort(); @@ -109,7 +112,7 @@ public class ServerInstance { int nettySecPort = serverConf.getNettyTlsPort(); LOGGER.info("Initializing TLS-secured Netty query server on port: {}", nettySecPort); _nettyTlsQueryServer = new QueryServer(nettySecPort, _queryScheduler, _serverMetrics, tlsConfig, - _accessControlFactory); + _accessControl); } else { _nettyTlsQueryServer = null; } @@ -121,7 +124,7 @@ public class ServerInstance { int grpcPort = serverConf.getGrpcPort(); LOGGER.info("Initializing gRPC query server on port: {}", grpcPort); - _grpcQueryServer = new GrpcQueryServer(grpcPort, _queryExecutor, _serverMetrics); + _grpcQueryServer = new GrpcQueryServer(grpcPort, _queryExecutor, _serverMetrics, _accessControl); } else { _grpcQueryServer = null; } diff --git a/pinot-server/src/test/java/org/apache/pinot/server/api/AccessControlTest.java b/pinot-server/src/test/java/org/apache/pinot/server/api/AccessControlTest.java index 1d6d775..10a334d 100644 --- a/pinot-server/src/test/java/org/apache/pinot/server/api/AccessControlTest.java +++ b/pinot-server/src/test/java/org/apache/pinot/server/api/AccessControlTest.java @@ -18,27 +18,35 @@ */ package org.apache.pinot.server.api; +import com.google.common.collect.ImmutableMap; import io.netty.channel.ChannelHandlerContext; import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import org.apache.commons.io.FileUtils; +import org.apache.pinot.core.auth.BasicAuthUtils; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.transport.TlsConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManager; import org.apache.pinot.server.access.AccessControl; import org.apache.pinot.server.access.AccessControlFactory; +import org.apache.pinot.server.access.BasicAuthAccessFactory; +import org.apache.pinot.server.access.GrpcRequesterIdentity; +import org.apache.pinot.server.access.HttpRequesterIdentity; +import org.apache.pinot.server.access.RequesterIdentity; import org.apache.pinot.server.starter.ServerInstance; import org.apache.pinot.server.starter.helix.AdminApiApplication; import org.apache.pinot.server.starter.helix.DefaultHelixStarterServerConfig; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.NetUtils; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -51,7 +59,6 @@ public class AccessControlTest { private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "AccessControlTest"); protected static final String TABLE_NAME = "testTable"; - private final Map<String, TableDataManager> _tableDataManagerMap = new HashMap<>(); private AdminApiApplication _adminApiApplication; protected WebTarget _webTarget; @@ -74,12 +81,14 @@ public class AccessControlTest { serverConf.setProperty(CommonConstants.Server.CONFIG_OF_INSTANCE_ID, CommonConstants.Helix.PREFIX_OF_SERVER_INSTANCE + hostname + "_" + port); _adminApiApplication = new AdminApiApplication(serverInstance, new DenyAllAccessFactory(), serverConf); + + int adminApiApplicationPort = getAvailablePort(); _adminApiApplication.start(Collections.singletonList( - new ListenerConfig(CommonConstants.HTTP_PROTOCOL, "0.0.0.0", CommonConstants.Server.DEFAULT_ADMIN_API_PORT, + new ListenerConfig(CommonConstants.HTTP_PROTOCOL, "0.0.0.0", adminApiApplicationPort, CommonConstants.HTTP_PROTOCOL, new TlsConfig()))); _webTarget = ClientBuilder.newClient().target( - String.format("http://%s:%d", NetUtils.getHostAddress(), CommonConstants.Server.DEFAULT_ADMIN_API_PORT)); + String.format("http://%s:%d", NetUtils.getHostAddress(), adminApiApplicationPort)); } @AfterClass @@ -104,7 +113,7 @@ public class AccessControlTest { } @Override - public boolean hasDataAccess(HttpHeaders httpHeaders, String tableName) { + public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { return false; } }; @@ -114,4 +123,66 @@ public class AccessControlTest { return DENY_ALL_ACCESS; } } + + @Test + public void testGrpcBasicAuth() { + testBasicAuth(new GrpcRequesterIdentity( + ImmutableMap.of("authorization", BasicAuthUtils.toBasicAuthToken("admin123", "verysecret"))), true); + testBasicAuth(new GrpcRequesterIdentity( + ImmutableMap.of("authorization", BasicAuthUtils.toBasicAuthToken("user456", "kindasecret"))), false); + + testBasicAuth(new GrpcRequesterIdentity( + ImmutableMap.of("authorization", "Basic YWRtaW4xMjM6dmVyeXNlY3JldA")), true); + testBasicAuth(new GrpcRequesterIdentity( + ImmutableMap.of("authorization", "Basic dXNlcjQ1NjpraW5kYXNlY3JldA==")), false); + } + + @Test + public void testHttpBasicAuth() { + HttpHeaders headers = new ContainerRequest(null, null, null, null, new MapPropertiesDelegate()); + headers.getRequestHeaders() + .put("authorization", Arrays.asList(BasicAuthUtils.toBasicAuthToken("admin123", "verysecret"))); + testBasicAuth(new HttpRequesterIdentity(headers), true); + headers.getRequestHeaders() + .put("authorization", Arrays.asList(BasicAuthUtils.toBasicAuthToken("user456", "kindasecret"))); + testBasicAuth(new HttpRequesterIdentity(headers), false); + headers.getRequestHeaders().put("authorization", Arrays.asList("Basic YWRtaW4xMjM6dmVyeXNlY3JldA")); + testBasicAuth(new HttpRequesterIdentity(headers), true); + headers.getRequestHeaders().put("authorization", Arrays.asList("Basic dXNlcjQ1NjpraW5kYXNlY3JldA==")); + testBasicAuth(new HttpRequesterIdentity(headers), false); + } + + public void testBasicAuth(RequesterIdentity requesterIdentity, boolean isAdmin) { + final BasicAuthAccessFactory basicAuthAccessFactory = new BasicAuthAccessFactory(); + PinotConfiguration config = new PinotConfiguration(ImmutableMap.of("principals", "admin123,user456", + "principals.admin123.password", "verysecret", "principals.user456.password", "kindasecret", + "principals.user456.tables", "stuff,lessImportantStuff")); + basicAuthAccessFactory.init(config); + final AccessControl accessControl = basicAuthAccessFactory.create(); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "stuff")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "stuff_OFFLINE")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "stuff_REALTIME")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "lessImportantStuff")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "lessImportantStuff_OFFLINE")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "lessImportantStuff_REALTIME")); + if (isAdmin) { + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "myTable")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "myTable_OFFLINE")); + Assert.assertTrue(accessControl.hasDataAccess(requesterIdentity, "myTable_REALTIME")); + } else { + Assert.assertFalse(accessControl.hasDataAccess(requesterIdentity, "myTable")); + Assert.assertFalse(accessControl.hasDataAccess(requesterIdentity, "myTable_OFFLINE")); + Assert.assertFalse(accessControl.hasDataAccess(requesterIdentity, "myTable_REALTIME")); + } + } + + public static int getAvailablePort() { + try { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } + } catch (IOException e) { + throw new RuntimeException("Failed to find an available port to use", e); + } + } } diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java index 721e3d8..0947b2f 100644 --- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java +++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java @@ -403,6 +403,7 @@ public class CommonConstants { public static final String ACCESS_CONTROL_FACTORY_CLASS = "pinot.server.admin.access.control.factory.class"; public static final String DEFAULT_ACCESS_CONTROL_FACTORY_CLASS = "org.apache.pinot.server.access.AllowAllAccessFactory"; + public static final String PREFIX_OF_CONFIG_OF_ACCESS_CONTROL = "pinot.server.admin.access.control"; public static final String CONFIG_OF_ENABLE_THREAD_CPU_TIME_MEASUREMENT = "pinot.server.instance.enableThreadCpuTimeMeasurement"; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org