diff --git a/docs/src/main/sphinx/connector/pinot.rst b/docs/src/main/sphinx/connector/pinot.rst
index fcf3fa93e442..fbc9a7582231 100644
--- a/docs/src/main/sphinx/connector/pinot.rst
+++ b/docs/src/main/sphinx/connector/pinot.rst
@@ -33,18 +33,35 @@ Configuration properties
The following configuration properties are available:
-============================== ========== ==============================================================================
-Property Name Required Description
-============================== ========== ==============================================================================
-``pinot.controller-urls`` Yes A comma separated list of controller hosts. If Pinot is deployed via
- `Kubernetes `_ this needs to point to the controller
- service endpoint. The Pinot broker and server must be accessible via DNS as
- Pinot returns hostnames and not IP addresses.
-``pinot.segments-per-split`` No The number of segments processed in a split. Setting this higher reduces the
- number of requests made to Pinot. This is useful for smaller Pinot clusters.
-``pinot.request-timeout`` No The timeout for Pinot requests. Increasing this can reduce timeouts if DNS
- resolution is slow.
-============================== ========== ==============================================================================
+============================================= ========== ==============================================================================
+Property Name Required Description
+============================================= ========== ==============================================================================
+``pinot.controller-urls`` Yes A comma separated list of controller hosts. If Pinot is deployed via
+ `Kubernetes `_ this needs to point to the controller
+ service endpoint. The Pinot broker and server must be accessible via DNS as
+ Pinot returns hostnames and not IP addresses.
+``pinot.segments-per-split`` No The number of segments processed in a split. Setting this higher reduces the
+ number of requests made to Pinot. This is useful for smaller Pinot clusters.
+``pinot.request-timeout`` No The timeout for Pinot requests. Increasing this can reduce timeouts if DNS
+ resolution is slow.
+``pinot.controller.authentication.type`` No Pinot authentication method for controller requests. Allowed values are
+ ``NONE`` and ``PASSWORD`` - defaults to ``NONE`` which is no authentication.
+``pinot.controller.authentication.user`` No Controller username for basic authentication method.
+``pinot.controller.authentication.password`` No Controller password for basic authentication method.
+``pinot.broker.authentication.type`` No Pinot authentication method for broker requests. Allowed values are
+ ``NONE`` and ``PASSWORD`` - defaults to ``NONE`` which is no
+ authentication.
+``pinot.broker.authentication.user`` No Broker username for basic authentication method.
+``pinot.broker.authentication.password`` No Broker password for basic authentication method.
+============================================= ========== ==============================================================================
+
+If ``pinot.controller.authentication.type`` is set to ``PASSWORD`` then both ``pinot.controller.authentication.user`` and
+``pinot.controller.authentication.password`` are required.
+
+If ``pinot.broker.authentication.type`` is set to ``PASSWORD`` then both ``pinot.broker.authentication.user`` and
+``pinot.broker.authentication.password`` are required.
+
+You can use :doc:`secrets ` to avoid actual values in the catalog properties files.
Querying Pinot tables
---------------------
diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml
index c7849443697f..d15ec86e87de 100755
--- a/plugin/trino-pinot/pom.xml
+++ b/plugin/trino-pinot/pom.xml
@@ -103,6 +103,18 @@
guice
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+ org.codehaus.mojo
+ *
+
+
+
+
commons-codec
commons-codec
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java
index 4d00f92ef067..9956d2fbfc39 100755
--- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotConnectorFactory.java
@@ -19,6 +19,7 @@
import io.airlift.bootstrap.Bootstrap;
import io.airlift.json.JsonModule;
import io.trino.plugin.base.TypeDeserializerModule;
+import io.trino.plugin.pinot.auth.PinotAuthenticationModule;
import io.trino.spi.connector.Connector;
import io.trino.spi.connector.ConnectorContext;
import io.trino.spi.connector.ConnectorFactory;
@@ -62,7 +63,8 @@ public Connector create(String catalogName, Map config, Connecto
.add(new JsonModule())
.add(new MBeanModule())
.add(new TypeDeserializerModule(context.getTypeManager()))
- .add(new PinotModule(catalogName, context.getNodeManager()));
+ .add(new PinotModule(catalogName, context.getNodeManager()))
+ .add(new PinotAuthenticationModule());
extension.ifPresent(modulesBuilder::add);
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java
new file mode 100644
index 000000000000..f17b80d05f61
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationModule.java
@@ -0,0 +1,127 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import io.airlift.configuration.AbstractConfigurationAwareModule;
+import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider;
+import io.trino.plugin.pinot.auth.password.PinotPasswordAuthenticationProvider;
+import io.trino.plugin.pinot.auth.password.inline.PinotPasswordBrokerAuthenticationConfig;
+import io.trino.plugin.pinot.auth.password.inline.PinotPasswordControllerAuthenticationConfig;
+
+import javax.inject.Singleton;
+
+import static io.airlift.configuration.ConditionalModule.conditionalModule;
+import static io.airlift.configuration.ConfigBinder.configBinder;
+import static io.trino.plugin.pinot.auth.PinotAuthenticationType.NONE;
+import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD;
+
+public class PinotAuthenticationModule
+ extends AbstractConfigurationAwareModule
+{
+ @Override
+ protected void setup(Binder binder)
+ {
+ bindAuthenticationProviderModule(
+ NONE,
+ new PinotNoneControllerAuthenticationProviderModule(),
+ new PinotNoneBrokerAuthenticationProviderModule());
+ bindAuthenticationProviderModule(
+ PASSWORD,
+ new PinotPasswordControllerAuthenticationProviderModule(),
+ new PinotPasswordBrokerAuthenticationProviderModule());
+ }
+
+ private void bindAuthenticationProviderModule(PinotAuthenticationType authType, Module controllerModule, Module brokerModule)
+ {
+ install(conditionalModule(
+ PinotAuthenticationTypeConfig.class,
+ config -> authType == config.getControllerAuthenticationType(),
+ controllerModule));
+ install(conditionalModule(
+ PinotAuthenticationTypeConfig.class,
+ config -> authType == config.getBrokerAuthenticationType(),
+ brokerModule));
+ }
+
+ private static class PinotNoneControllerAuthenticationProviderModule
+ implements Module
+ {
+ @Override
+ public void configure(Binder binder)
+ {
+ }
+
+ @Provides
+ @Singleton
+ public PinotControllerAuthenticationProvider getAuthenticationProvider()
+ {
+ return PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance());
+ }
+ }
+
+ private static class PinotNoneBrokerAuthenticationProviderModule
+ implements Module
+ {
+ @Override
+ public void configure(Binder binder)
+ {
+ }
+
+ @Provides
+ @Singleton
+ public PinotBrokerAuthenticationProvider getAuthenticationProvider()
+ {
+ return PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance());
+ }
+ }
+
+ private static class PinotPasswordControllerAuthenticationProviderModule
+ implements Module
+ {
+ @Override
+ public void configure(Binder binder)
+ {
+ configBinder(binder).bindConfig(PinotPasswordControllerAuthenticationConfig.class);
+ }
+
+ @Provides
+ @Singleton
+ public PinotControllerAuthenticationProvider getAuthenticationProvider(
+ PinotPasswordControllerAuthenticationConfig config)
+ {
+ return PinotControllerAuthenticationProvider.create(new PinotPasswordAuthenticationProvider(config.getUser(), config.getPassword()));
+ }
+ }
+
+ private static class PinotPasswordBrokerAuthenticationProviderModule
+ implements Module
+ {
+ @Override
+ public void configure(Binder binder)
+ {
+ configBinder(binder).bindConfig(PinotPasswordBrokerAuthenticationConfig.class);
+ }
+
+ @Provides
+ @Singleton
+ public PinotBrokerAuthenticationProvider getAuthenticationProvider(
+ PinotPasswordBrokerAuthenticationConfig config)
+ {
+ return PinotBrokerAuthenticationProvider.create(new PinotPasswordAuthenticationProvider(config.getUser(), config.getPassword()));
+ }
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java
new file mode 100644
index 000000000000..27be84c4e7d0
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationProvider.java
@@ -0,0 +1,21 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import java.util.Optional;
+
+public interface PinotAuthenticationProvider
+{
+ Optional getAuthenticationToken();
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java
new file mode 100644
index 000000000000..f10a78cd6aea
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationType.java
@@ -0,0 +1,19 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+public enum PinotAuthenticationType {
+ NONE,
+ PASSWORD,
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java
new file mode 100644
index 000000000000..8827ddece888
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotAuthenticationTypeConfig.java
@@ -0,0 +1,50 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import io.airlift.configuration.Config;
+
+import javax.validation.constraints.NotNull;
+
+public class PinotAuthenticationTypeConfig
+{
+ private PinotAuthenticationType controllerAuthenticationType = PinotAuthenticationType.NONE;
+ private PinotAuthenticationType brokerAuthenticationType = PinotAuthenticationType.NONE;
+
+ @NotNull
+ public PinotAuthenticationType getControllerAuthenticationType()
+ {
+ return controllerAuthenticationType;
+ }
+
+ @Config("pinot.controller.authentication.type")
+ public PinotAuthenticationTypeConfig setControllerAuthenticationType(PinotAuthenticationType controllerAuthenticationType)
+ {
+ this.controllerAuthenticationType = controllerAuthenticationType;
+ return this;
+ }
+
+ @NotNull
+ public PinotAuthenticationType getBrokerAuthenticationType()
+ {
+ return brokerAuthenticationType;
+ }
+
+ @Config("pinot.broker.authentication.type")
+ public PinotAuthenticationTypeConfig setBrokerAuthenticationType(PinotAuthenticationType brokerAuthenticationType)
+ {
+ this.brokerAuthenticationType = brokerAuthenticationType;
+ return this;
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java
new file mode 100644
index 000000000000..45070e5b7259
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotBrokerAuthenticationProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+
+public class PinotBrokerAuthenticationProvider
+ implements PinotAuthenticationProvider
+{
+ public static PinotBrokerAuthenticationProvider create(PinotAuthenticationProvider delegate)
+ {
+ return new PinotBrokerAuthenticationProvider(delegate);
+ }
+
+ private final PinotAuthenticationProvider delegate;
+
+ private PinotBrokerAuthenticationProvider(PinotAuthenticationProvider delegate)
+ {
+ this.delegate = requireNonNull(delegate, "Delegate broker authentication provider is required");
+ }
+
+ @Override
+ public Optional getAuthenticationToken()
+ {
+ return delegate.getAuthenticationToken();
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java
new file mode 100644
index 000000000000..4927ce609efe
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/PinotControllerAuthenticationProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+
+public class PinotControllerAuthenticationProvider
+ implements PinotAuthenticationProvider
+{
+ public static PinotControllerAuthenticationProvider create(PinotAuthenticationProvider delegate)
+ {
+ return new PinotControllerAuthenticationProvider(delegate);
+ }
+
+ private final PinotAuthenticationProvider delegate;
+
+ private PinotControllerAuthenticationProvider(PinotAuthenticationProvider delegate)
+ {
+ this.delegate = requireNonNull(delegate, "Delegate controller authentication provider is required");
+ }
+
+ @Override
+ public Optional getAuthenticationToken()
+ {
+ return delegate.getAuthenticationToken();
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java
new file mode 100644
index 000000000000..68f36528e8c2
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/none/PinotEmptyAuthenticationProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.none;
+
+import io.trino.plugin.pinot.auth.PinotAuthenticationProvider;
+
+import java.util.Optional;
+
+public class PinotEmptyAuthenticationProvider
+ implements PinotAuthenticationProvider
+{
+ private static final PinotEmptyAuthenticationProvider INSTANCE = new PinotEmptyAuthenticationProvider();
+
+ public static PinotEmptyAuthenticationProvider instance()
+ {
+ return INSTANCE;
+ }
+
+ private PinotEmptyAuthenticationProvider() {}
+
+ @Override
+ public Optional getAuthenticationToken()
+ {
+ return Optional.empty();
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java
new file mode 100644
index 000000000000..0c59b3e4fe98
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/PinotPasswordAuthenticationProvider.java
@@ -0,0 +1,46 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password;
+
+import io.trino.plugin.pinot.auth.PinotAuthenticationProvider;
+import okhttp3.Credentials;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+
+public class PinotPasswordAuthenticationProvider
+ implements PinotAuthenticationProvider
+{
+ private final Optional authToken;
+
+ public PinotPasswordAuthenticationProvider(String user, String password)
+ {
+ requireNonNull(user, "user is null");
+ requireNonNull(password, "password is null");
+ this.authToken = Optional.of(encode(user, password));
+ }
+
+ @Override
+ public Optional getAuthenticationToken()
+ {
+ return authToken;
+ }
+
+ private String encode(String username, String password)
+ {
+ return Credentials.basic(username, password, StandardCharsets.ISO_8859_1);
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java
new file mode 100755
index 000000000000..f2124c06dc9b
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordBrokerAuthenticationConfig.java
@@ -0,0 +1,52 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password.inline;
+
+import io.airlift.configuration.Config;
+import io.airlift.configuration.ConfigSecuritySensitive;
+
+import javax.validation.constraints.NotNull;
+
+public class PinotPasswordBrokerAuthenticationConfig
+{
+ private String user;
+ private String password;
+
+ @NotNull
+ public String getUser()
+ {
+ return user;
+ }
+
+ @Config("pinot.broker.authentication.user")
+ public PinotPasswordBrokerAuthenticationConfig setUser(String user)
+ {
+ this.user = user;
+ return this;
+ }
+
+ @NotNull
+ public String getPassword()
+ {
+ return password;
+ }
+
+ @Config("pinot.broker.authentication.password")
+ @ConfigSecuritySensitive
+ public PinotPasswordBrokerAuthenticationConfig setPassword(String password)
+ {
+ this.password = password;
+ return this;
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java
new file mode 100755
index 000000000000..05054437bf53
--- /dev/null
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/auth/password/inline/PinotPasswordControllerAuthenticationConfig.java
@@ -0,0 +1,52 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password.inline;
+
+import io.airlift.configuration.Config;
+import io.airlift.configuration.ConfigSecuritySensitive;
+
+import javax.validation.constraints.NotNull;
+
+public class PinotPasswordControllerAuthenticationConfig
+{
+ private String user;
+ private String password;
+
+ @NotNull
+ public String getUser()
+ {
+ return user;
+ }
+
+ @Config("pinot.controller.authentication.user")
+ public PinotPasswordControllerAuthenticationConfig setUser(String user)
+ {
+ this.user = user;
+ return this;
+ }
+
+ @NotNull
+ public String getPassword()
+ {
+ return password;
+ }
+
+ @Config("pinot.controller.authentication.password")
+ @ConfigSecuritySensitive
+ public PinotPasswordControllerAuthenticationConfig setPassword(String password)
+ {
+ this.password = password;
+ return this;
+ }
+}
diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java
index 6cc8377a6b01..5b91c052fbd4 100755
--- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java
+++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/client/PinotClient.java
@@ -24,7 +24,8 @@
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.net.HttpHeaders;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
import io.airlift.http.client.HttpClient;
import io.airlift.http.client.JsonResponseHandler;
import io.airlift.http.client.Request;
@@ -41,6 +42,8 @@
import io.trino.plugin.pinot.PinotException;
import io.trino.plugin.pinot.PinotInsufficientServerResponseException;
import io.trino.plugin.pinot.PinotSessionProperties;
+import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider;
+import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider;
import io.trino.plugin.pinot.query.PinotQueryInfo;
import io.trino.spi.connector.ConnectorSession;
import org.apache.pinot.common.response.broker.BrokerResponseNative;
@@ -69,6 +72,9 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.net.HttpHeaders.ACCEPT;
+import static com.google.common.net.HttpHeaders.AUTHORIZATION;
+import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static io.airlift.http.client.HttpUriBuilder.uriBuilderFrom;
import static io.airlift.http.client.JsonResponseHandler.createJsonResponseHandler;
import static io.airlift.json.JsonCodec.jsonCodec;
@@ -110,6 +116,8 @@ public class PinotClient
private final JsonCodec timeBoundaryJsonCodec;
private final JsonCodec schemaJsonCodec;
private final JsonCodec brokerResponseCodec;
+ private final PinotControllerAuthenticationProvider controllerAuthenticationProvider;
+ private final PinotBrokerAuthenticationProvider brokerAuthenticationProvider;
@Inject
public PinotClient(
@@ -119,7 +127,9 @@ public PinotClient(
JsonCodec tablesJsonCodec,
JsonCodec brokersForTableJsonCodec,
JsonCodec timeBoundaryJsonCodec,
- JsonCodec brokerResponseCodec)
+ JsonCodec brokerResponseCodec,
+ PinotControllerAuthenticationProvider controllerAuthenticationProvider,
+ PinotBrokerAuthenticationProvider brokerAuthenticationProvider)
{
this.brokersForTableJsonCodec = requireNonNull(brokersForTableJsonCodec, "brokersForTableJsonCodec is null");
this.timeBoundaryJsonCodec = requireNonNull(timeBoundaryJsonCodec, "timeBoundaryJsonCodec is null");
@@ -138,6 +148,8 @@ public PinotClient(
this.brokersForTableCache = CacheBuilder.newBuilder()
.expireAfterWrite(config.getMetadataCacheExpiry().roundTo(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
.build((CacheLoader.from(this::getAllBrokersForTable)));
+ this.controllerAuthenticationProvider = controllerAuthenticationProvider;
+ this.brokerAuthenticationProvider = brokerAuthenticationProvider;
}
public static JsonCodecBinder addJsonBinders(JsonCodecBinder jsonCodecBinder)
@@ -150,11 +162,16 @@ public static JsonCodecBinder addJsonBinders(JsonCodecBinder jsonCodecBinder)
return jsonCodecBinder;
}
- protected T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Optional requestBody, JsonCodec codec)
+ protected T doHttpActionWithHeadersJson(
+ Request.Builder requestBuilder,
+ Optional requestBody,
+ JsonCodec codec,
+ Multimap additionalHeaders)
{
- requestBuilder.setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON);
+ requestBuilder.addHeaders(additionalHeaders);
+ requestBuilder.setHeader(ACCEPT, APPLICATION_JSON);
if (requestBody.isPresent()) {
- requestBuilder.setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
+ requestBuilder.setHeader(CONTENT_TYPE, APPLICATION_JSON);
requestBuilder.setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(requestBody.get(), StandardCharsets.UTF_8));
}
Request request = requestBuilder.build();
@@ -180,18 +197,24 @@ protected T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Opti
private T sendHttpGetToControllerJson(String path, JsonCodec codec)
{
+ ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder();
+ controllerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token));
return doHttpActionWithHeadersJson(
Request.Builder.prepareGet().setUri(uriBuilderFrom(getControllerUrl()).appendPath(path).build()),
Optional.empty(),
- codec);
+ codec,
+ additionalHeadersBuilder.build());
}
private T sendHttpGetToBrokerJson(String table, String path, JsonCodec codec)
{
+ ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder();
+ brokerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token));
return doHttpActionWithHeadersJson(
Request.Builder.prepareGet().setUri(URI.create(format("http://%s/%s", getBrokerHost(table), path))),
Optional.empty(),
- codec);
+ codec,
+ additionalHeadersBuilder.build());
}
private URI getControllerUrl()
@@ -448,7 +471,10 @@ private BrokerResponseNative submitBrokerQueryJson(ConnectorSession session, Pin
LOG.info("Query '%s' on broker host '%s'", queryHost, query.getQuery());
Request.Builder builder = Request.Builder.preparePost()
.setUri(URI.create(format(QUERY_URL_TEMPLATE, queryHost)));
- BrokerResponseNative response = doHttpActionWithHeadersJson(builder, Optional.of(queryRequest), brokerResponseCodec);
+ ImmutableMultimap.Builder additionalHeadersBuilder = ImmutableMultimap.builder();
+ brokerAuthenticationProvider.getAuthenticationToken().ifPresent(token -> additionalHeadersBuilder.put(AUTHORIZATION, token));
+ BrokerResponseNative response = doHttpActionWithHeadersJson(builder, Optional.of(queryRequest), brokerResponseCodec,
+ additionalHeadersBuilder.build());
if (response.getExceptionsSize() > 0 && response.getProcessingExceptions() != null && !response.getProcessingExceptions().isEmpty()) {
// Pinot is known to return exceptions with benign errorcodes like 200
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java
similarity index 99%
rename from plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java
rename to plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java
index e39caa072fa6..bddc8863fe53 100644
--- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotIntegrationSmokeTest.java
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/AbstractPinotIntegrationSmokeTest.java
@@ -65,7 +65,7 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.testng.Assert.assertEquals;
-public class TestPinotIntegrationSmokeTest
+public abstract class AbstractPinotIntegrationSmokeTest
// TODO extend BaseConnectorTest
extends AbstractTestQueryFramework
{
@@ -87,13 +87,15 @@ public class TestPinotIntegrationSmokeTest
// Use a fixed instant for testing date time functions
private static final Instant CREATED_AT_INSTANT = Instant.parse("2021-05-10T00:00:00.00Z");
+ protected abstract boolean isSecured();
+
@Override
protected QueryRunner createQueryRunner()
throws Exception
{
TestingKafka kafka = closeAfterClass(TestingKafka.createWithSchemaRegistry());
kafka.start();
- TestingPinotCluster pinot = closeAfterClass(new TestingPinotCluster(kafka.getNetwork()));
+ TestingPinotCluster pinot = closeAfterClass(new TestingPinotCluster(kafka.getNetwork(), isSecured()));
pinot.start();
// Create and populate the all_types topic and table
@@ -294,17 +296,26 @@ protected QueryRunner createQueryRunner()
pinot.createSchema(getClass().getClassLoader().getResourceAsStream("quotes_in_column_name_schema.json"), QUOTES_IN_COLUMN_NAME_TABLE);
pinot.addRealTimeTable(getClass().getClassLoader().getResourceAsStream("quotes_in_column_name_realtimeSpec.json"), QUOTES_IN_COLUMN_NAME_TABLE);
- Map pinotProperties = ImmutableMap.builder()
+ return PinotQueryRunner.createPinotQueryRunner(
+ ImmutableMap.of(),
+ pinotProperties(pinot),
+ Optional.of(binder -> newOptionalBinder(binder, PinotHostMapper.class).setBinding()
+ .toInstance(new TestingPinotHostMapper(pinot.getBrokerHostAndPort(), pinot.getServerHostAndPort()))));
+ }
+
+ private Map pinotProperties(TestingPinotCluster pinot)
+ {
+ return ImmutableMap.builder()
.put("pinot.controller-urls", pinot.getControllerConnectString())
.put("pinot.max-rows-per-split-for-segment-queries", String.valueOf(MAX_ROWS_PER_SPLIT_FOR_SEGMENT_QUERIES))
.put("pinot.max-rows-for-broker-queries", String.valueOf(MAX_ROWS_PER_SPLIT_FOR_BROKER_QUERIES))
+ .putAll(additionalPinotProperties())
.buildOrThrow();
+ }
- return PinotQueryRunner.createPinotQueryRunner(
- ImmutableMap.of(),
- pinotProperties,
- Optional.of(binder -> newOptionalBinder(binder, PinotHostMapper.class).setBinding()
- .toInstance(new TestingPinotHostMapper(pinot.getBrokerHostAndPort(), pinot.getServerHostAndPort()))));
+ protected Map additionalPinotProperties()
+ {
+ return ImmutableMap.of();
}
private static Map schemaRegistryAwareProducer(TestingKafka testingKafka)
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java
index 2f393ae93a85..e7a9d11d27ea 100755
--- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/MockPinotClient.java
@@ -15,9 +15,13 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
import io.airlift.http.client.Request;
import io.airlift.http.client.testing.TestingHttpClient;
import io.airlift.json.JsonCodec;
+import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider;
+import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider;
+import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider;
import io.trino.plugin.pinot.client.IdentityPinotHostMapper;
import io.trino.plugin.pinot.client.PinotClient;
import org.apache.pinot.spi.data.Schema;
@@ -57,7 +61,9 @@ public MockPinotClient(PinotConfig pinotConfig, Map metadata, St
TABLES_JSON_CODEC,
BROKERS_FOR_TABLE_JSON_CODEC,
TIME_BOUNDARY_JSON_CODEC,
- BROKER_RESPONSE_NATIVE_JSON_CODEC);
+ BROKER_RESPONSE_NATIVE_JSON_CODEC,
+ PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()),
+ PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()));
this.metadata = metadata;
this.response = response;
}
@@ -69,7 +75,11 @@ public String getBrokerHost(String table)
}
@Override
- public T doHttpActionWithHeadersJson(Request.Builder requestBuilder, Optional requestBody, JsonCodec codec)
+ public T doHttpActionWithHeadersJson(
+ Request.Builder requestBuilder,
+ Optional requestBody,
+ JsonCodec codec,
+ Multimap additionalHeaders)
{
return codec.fromJson(response);
}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java
index c14f6897f3df..683740323121 100755
--- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/PinotQueryRunner.java
@@ -32,9 +32,7 @@ public class PinotQueryRunner
{
public static final String PINOT_CATALOG = "pinot";
- private PinotQueryRunner()
- {
- }
+ private PinotQueryRunner() {}
public static DistributedQueryRunner createPinotQueryRunner(Map extraProperties, Map extraPinotProperties, Optional extension)
throws Exception
@@ -70,7 +68,7 @@ public static void main(String[] args)
Logging.initialize();
TestingKafka kafka = TestingKafka.createWithSchemaRegistry();
kafka.start();
- TestingPinotCluster pinot = new TestingPinotCluster(kafka.getNetwork());
+ TestingPinotCluster pinot = new TestingPinotCluster(kafka.getNetwork(), false);
pinot.start();
Map properties = ImmutableMap.of("http-server.http.port", "8080");
Map pinotProperties = ImmutableMap.builder()
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java
index 3fc2e6f7a67c..e5d9e1f5d8b0 100755
--- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java
@@ -20,6 +20,9 @@
import io.airlift.http.client.testing.TestingHttpClient;
import io.airlift.http.client.testing.TestingResponse;
import io.airlift.units.Duration;
+import io.trino.plugin.pinot.auth.PinotBrokerAuthenticationProvider;
+import io.trino.plugin.pinot.auth.PinotControllerAuthenticationProvider;
+import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider;
import io.trino.plugin.pinot.client.IdentityPinotHostMapper;
import io.trino.plugin.pinot.client.PinotClient;
import io.trino.testing.assertions.Assert;
@@ -74,7 +77,16 @@ public void testBrokersParsed()
PinotConfig pinotConfig = new PinotConfig()
.setMetadataCacheExpiry(new Duration(0, TimeUnit.MILLISECONDS))
.setControllerUrls("localhost:7900");
- PinotClient pinotClient = new PinotClient(pinotConfig, new IdentityPinotHostMapper(), httpClient, MetadataUtil.TABLES_JSON_CODEC, MetadataUtil.BROKERS_FOR_TABLE_JSON_CODEC, MetadataUtil.TIME_BOUNDARY_JSON_CODEC, MetadataUtil.BROKER_RESPONSE_NATIVE_JSON_CODEC);
+ PinotClient pinotClient = new PinotClient(
+ pinotConfig,
+ new IdentityPinotHostMapper(),
+ httpClient,
+ MetadataUtil.TABLES_JSON_CODEC,
+ MetadataUtil.BROKERS_FOR_TABLE_JSON_CODEC,
+ MetadataUtil.TIME_BOUNDARY_JSON_CODEC,
+ MetadataUtil.BROKER_RESPONSE_NATIVE_JSON_CODEC,
+ PinotControllerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()),
+ PinotBrokerAuthenticationProvider.create(PinotEmptyAuthenticationProvider.instance()));
ImmutableSet brokers = ImmutableSet.copyOf(pinotClient.getAllBrokersForTable("dummy"));
Assert.assertEquals(ImmutableSet.of("dummy-broker-host1-datacenter1:6513", "dummy-broker-host2-datacenter1:6513", "dummy-broker-host3-datacenter1:6513", "dummy-broker-host4-datacenter1:6513"), brokers);
}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java
new file mode 100644
index 000000000000..54d1008c3475
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotWithoutAuthenticationIntegrationSmokeTest.java
@@ -0,0 +1,24 @@
+/*
+ * 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 io.trino.plugin.pinot;
+
+public class TestPinotWithoutAuthenticationIntegrationSmokeTest
+ extends AbstractPinotIntegrationSmokeTest
+{
+ @Override
+ protected boolean isSecured()
+ {
+ return false;
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java
new file mode 100644
index 000000000000..189b6236a327
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestSecuredPinotIntegrationSmokeTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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 io.trino.plugin.pinot;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD;
+
+public class TestSecuredPinotIntegrationSmokeTest
+ extends AbstractPinotIntegrationSmokeTest
+{
+ @Override
+ protected boolean isSecured()
+ {
+ return true;
+ }
+
+ @Override
+ protected Map additionalPinotProperties()
+ {
+ return ImmutableMap.builder()
+ .put("pinot.controller.authentication.type", PASSWORD.name())
+ .put("pinot.controller.authentication.user", "admin")
+ .put("pinot.controller.authentication.password", "verysecret")
+ .put("pinot.broker.authentication.type", PASSWORD.name())
+ .put("pinot.broker.authentication.user", "query")
+ .put("pinot.broker.authentication.password", "secret")
+ .buildOrThrow();
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java
index 399029a3d84d..fb935fb983c0 100644
--- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestingPinotCluster.java
@@ -24,6 +24,7 @@
import io.airlift.http.client.StaticBodyGenerator;
import io.airlift.http.client.jetty.JettyHttpClient;
import io.airlift.json.JsonCodec;
+import okhttp3.Credentials;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
@@ -32,6 +33,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Supplier;
@@ -64,8 +66,9 @@ public class TestingPinotCluster
private final GenericContainer> zookeeper;
private final HttpClient httpClient;
private final Closer closer = Closer.create();
+ private final boolean secured;
- public TestingPinotCluster(Network network)
+ public TestingPinotCluster(Network network, boolean secured)
{
httpClient = closer.register(new JettyHttpClient());
zookeeper = new GenericContainer<>(parse("zookeeper:3.5.6"))
@@ -75,20 +78,22 @@ public TestingPinotCluster(Network network)
.withExposedPorts(ZOOKEEPER_PORT);
closer.register(zookeeper::stop);
+ String controllerConfig = secured ? "/var/pinot/controller/config/pinot-controller-secured.conf" : "/var/pinot/controller/config/pinot-controller.conf";
controller = new GenericContainer<>(parse(BASE_IMAGE))
.withNetwork(network)
.withClasspathResourceMapping("/pinot-controller", "/var/pinot/controller/config", BindMode.READ_ONLY)
.withEnv("JAVA_OPTS", "-Xmx512m -Dlog4j2.configurationFile=/opt/pinot/conf/pinot-controller-log4j2.xml -Dplugins.dir=/opt/pinot/plugins")
- .withCommand("StartController", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf")
+ .withCommand("StartController", "-configFileName", controllerConfig)
.withNetworkAliases("pinot-controller", "localhost")
.withExposedPorts(CONTROLLER_PORT);
closer.register(controller::stop);
+ String brokerConfig = secured ? "/var/pinot/broker/config/pinot-broker-secured.conf" : "/var/pinot/broker/config/pinot-broker.conf";
broker = new GenericContainer<>(parse(BASE_IMAGE))
.withNetwork(network)
.withClasspathResourceMapping("/pinot-broker", "/var/pinot/broker/config", BindMode.READ_ONLY)
.withEnv("JAVA_OPTS", "-Xmx512m -Dlog4j2.configurationFile=/opt/pinot/conf/pinot-broker-log4j2.xml -Dplugins.dir=/opt/pinot/plugins")
- .withCommand("StartBroker", "-clusterName", "pinot", "-zkAddress", getZookeeperInternalHostPort(), "-configFileName", "/var/pinot/broker/config/pinot-broker.conf")
+ .withCommand("StartBroker", "-clusterName", "pinot", "-zkAddress", getZookeeperInternalHostPort(), "-configFileName", brokerConfig)
.withNetworkAliases("pinot-broker", "localhost")
.withExposedPorts(BROKER_PORT);
closer.register(broker::stop);
@@ -101,6 +106,8 @@ public TestingPinotCluster(Network network)
.withNetworkAliases("pinot-server", "localhost")
.withExposedPorts(SERVER_PORT, SERVER_ADMIN_PORT);
closer.register(server::stop);
+
+ this.secured = secured;
}
public void start()
@@ -146,6 +153,7 @@ public void createSchema(InputStream tableSchemaSpec, String tableName)
.setUri(getControllerUri("schemas"))
.setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
.setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
+ .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "")
.setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(bytes))
.build();
@@ -164,6 +172,7 @@ private void verifySchema(String tableName)
{
Request request = Request.Builder.prepareGet().setUri(getControllerUri("schemas"))
.setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
+ .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "")
.build();
doWithRetries(() -> {
List schemas = httpClient.execute(request, createJsonResponseHandler(LIST_JSON_CODEC));
@@ -180,6 +189,7 @@ public void addRealTimeTable(InputStream realTimeSpec, String tableName)
.setUri(getControllerUri("tables"))
.setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
.setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
+ .addHeader(HttpHeaders.AUTHORIZATION, secured ? controllerAuthToken() : "")
.setBodyGenerator(StaticBodyGenerator.createStaticBodyGenerator(bytes))
.build();
@@ -204,6 +214,12 @@ private static T doWithRetries(Supplier supplier, int retries)
throw exception;
}
+ private static String controllerAuthToken()
+ {
+ // Secrets defined in pinot-controller-secured.conf
+ return Credentials.basic("admin", "verysecret", StandardCharsets.ISO_8859_1);
+ }
+
public static class PinotSuccessResponse
{
private final String status;
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java
new file mode 100644
index 000000000000..01820e6b60d5
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java
@@ -0,0 +1,52 @@
+/*
+ * 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 io.trino.plugin.pinot.auth;
+
+import com.google.common.collect.ImmutableMap;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
+import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults;
+import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults;
+import static io.trino.plugin.pinot.auth.PinotAuthenticationType.NONE;
+import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD;
+
+public class TestPinotAuthenticationTypeConfig
+{
+ @Test
+ public void testDefaults()
+ {
+ assertRecordedDefaults(
+ recordDefaults(PinotAuthenticationTypeConfig.class)
+ .setControllerAuthenticationType(NONE)
+ .setBrokerAuthenticationType(NONE));
+ }
+
+ @Test
+ public void testExplicitPropertyMappings()
+ {
+ Map properties = new ImmutableMap.Builder()
+ .put("pinot.controller.authentication.type", "password")
+ .put("pinot.broker.authentication.type", "password")
+ .build();
+
+ PinotAuthenticationTypeConfig expected = new PinotAuthenticationTypeConfig()
+ .setControllerAuthenticationType(PASSWORD)
+ .setBrokerAuthenticationType(PASSWORD);
+
+ assertFullMapping(properties, expected);
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java
new file mode 100644
index 000000000000..18ab1701f557
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.none;
+
+import org.testng.annotations.Test;
+
+import java.util.Optional;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestPinotEmptyAuthenticationProvider
+{
+ @Test
+ public void testAuthenticationToken()
+ {
+ PinotEmptyAuthenticationProvider underTest = PinotEmptyAuthenticationProvider.instance();
+ assertEquals(underTest.getAuthenticationToken(), Optional.empty());
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java
new file mode 100644
index 000000000000..501bbb2289db
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password;
+
+import org.testng.annotations.Test;
+
+import java.util.Optional;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestPinotPasswordAuthenticationProvider
+{
+ @Test
+ public void testAuthenticationToken()
+ {
+ PinotPasswordAuthenticationProvider underTest = new PinotPasswordAuthenticationProvider("admin", "verysecret");
+ assertEquals(underTest.getAuthenticationToken(), Optional.of("Basic YWRtaW46dmVyeXNlY3JldA=="));
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java
new file mode 100644
index 000000000000..ba9afbc1459b
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java
@@ -0,0 +1,39 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password.inline;
+
+import com.google.common.collect.ImmutableMap;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
+
+public class TestPinotPasswordBrokerAuthenticationConfig
+{
+ @Test
+ public void testExplicitPropertyMappings()
+ {
+ Map properties = new ImmutableMap.Builder()
+ .put("pinot.broker.authentication.user", "query")
+ .put("pinot.broker.authentication.password", "secret")
+ .build();
+
+ PinotPasswordBrokerAuthenticationConfig expected = new PinotPasswordBrokerAuthenticationConfig()
+ .setUser("query")
+ .setPassword("secret");
+
+ assertFullMapping(properties, expected);
+ }
+}
diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java
new file mode 100644
index 000000000000..4c68698ec587
--- /dev/null
+++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java
@@ -0,0 +1,39 @@
+/*
+ * 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 io.trino.plugin.pinot.auth.password.inline;
+
+import com.google.common.collect.ImmutableMap;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
+
+public class TestPinotPasswordControllerAuthenticationConfig
+{
+ @Test
+ public void testExplicitPropertyMappings()
+ {
+ Map properties = new ImmutableMap.Builder()
+ .put("pinot.controller.authentication.user", "admin")
+ .put("pinot.controller.authentication.password", "verysecret")
+ .build();
+
+ PinotPasswordControllerAuthenticationConfig expected = new PinotPasswordControllerAuthenticationConfig()
+ .setUser("admin")
+ .setPassword("verysecret");
+
+ assertFullMapping(properties, expected);
+ }
+}
diff --git a/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf b/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf
new file mode 100644
index 000000000000..bbc9d7b771ec
--- /dev/null
+++ b/plugin/trino-pinot/src/test/resources/pinot-broker/pinot-broker-secured.conf
@@ -0,0 +1,6 @@
+pinot.broker.client.queryPort=8099
+pinot.broker.routing.table.builder.class=random
+pinot.set.instance.id.to.hostname=true
+pinot.broker.access.control.class=org.apache.pinot.broker.broker.BasicAuthAccessControlFactory
+pinot.broker.access.control.principals=query
+pinot.broker.access.control.principals.query.password=secret
diff --git a/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf b/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf
new file mode 100644
index 000000000000..1220c4559b3a
--- /dev/null
+++ b/plugin/trino-pinot/src/test/resources/pinot-controller/pinot-controller-secured.conf
@@ -0,0 +1,10 @@
+controller.helix.cluster.name=pinot
+controller.host=pinot-controller
+controller.port=9000
+controller.data.dir=/var/pinot/controller/data/data
+controller.local.temp.dir=/var/pinot/controller/data
+controller.zk.str=zookeeper:2181
+pinot.set.instance.id.to.hostname=true
+controller.admin.access.control.factory.class=org.apache.pinot.controller.api.access.BasicAuthAccessControlFactory
+controller.admin.access.control.principals=admin
+controller.admin.access.control.principals.admin.password=verysecret