From dfbca62c4a13053d264c026b9286c68caebd7321 Mon Sep 17 00:00:00 2001 From: Nishant Date: Mon, 16 Jan 2017 21:49:31 +0530 Subject: [PATCH 01/13] Add extension for supporting kerberos security - This PR adds an extension for supporting druid authentication via Kerberos. - Working on the docs. --- distribution/pom.xml | 2 + extensions-core/druid-kerberos/pom.xml | 186 ++++++++++++++++++ .../druid/kerberos/DruidKerberosConfig.java | 77 ++++++++ .../druid/kerberos/DruidKerberosModule.java | 91 +++++++++ .../io/druid/kerberos/KerberosHttpClient.java | 141 +++++++++++++ .../kerberos/KerberosHttpClientProvider.java | 55 ++++++ .../io/druid/kerberos/SpnegoFilterConfig.java | 124 ++++++++++++ .../io/druid/kerberos/SpnegoFilterHolder.java | 134 +++++++++++++ .../io.druid.initialization.DruidModule | 1 + .../kerberos/DruidKerberosConfigTest.java | 74 +++++++ .../kerberos/SpnegoFilterConfigTest.java | 75 +++++++ .../test/resources/test.runtime.properties | 7 + pom.xml | 1 + 13 files changed, 968 insertions(+) create mode 100644 extensions-core/druid-kerberos/pom.xml create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java create mode 100644 extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule create mode 100644 extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java create mode 100644 extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java create mode 100644 extensions-core/druid-kerberos/src/test/resources/test.runtime.properties diff --git a/distribution/pom.xml b/distribution/pom.xml index 044728d62911..0c7d6a2db85b 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -107,6 +107,8 @@ -c io.druid.extensions.contrib:scan-query ${druid.distribution.pulldeps.opts} + -c + io.druid.extensions:druid-kerberos diff --git a/extensions-core/druid-kerberos/pom.xml b/extensions-core/druid-kerberos/pom.xml new file mode 100644 index 000000000000..ce5bcd58e5e7 --- /dev/null +++ b/extensions-core/druid-kerberos/pom.xml @@ -0,0 +1,186 @@ + + + + + 4.0.0 + + io.druid.extensions + druid-kerberos + druid-kerberos + druid-kerberos + + + io.druid + druid + 0.9.3-SNAPSHOT + ../../pom.xml + + + + + io.druid + druid-processing + ${project.parent.version} + provided + + + io.druid + druid-server + ${project.parent.version} + provided + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-util + + + org.eclipse.jetty + jetty-proxy + + + org.eclipse.jetty + jetty-servlet + + + org.eclipse.jetty + jetty-servlets + + + org.apache.hadoop + hadoop-common + ${hadoop.compile.version} + + + commons-cli + commons-cli + + + commons-httpclient + commons-httpclient + + + log4j + log4j + + + commons-codec + commons-codec + + + commons-logging + commons-logging + + + commons-io + commons-io + + + commons-lang + commons-lang + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + org.apache.zookeeper + zookeeper + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + javax.ws.rs + jsr311-api + + + com.google.code.findbugs + jsr305 + + + org.mortbay.jetty + jetty-util + + + org.apache.hadoop + hadoop-annotations + + + javax.activation + activation + + + com.google.protobuf + protobuf-java + + + com.sun.jersey + jersey-core + + + + + + + io.druid + druid-processing + ${project.parent.version} + test + test-jar + + + junit + junit + test + + + org.easymock + easymock + test + + + org.eclipse.jetty + jetty-util + + + + diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java new file mode 100644 index 000000000000..f69655f263ad --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java @@ -0,0 +1,77 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DruidKerberosConfig +{ + @JsonProperty + private final String principal; + @JsonProperty + private final String keytab; + + @JsonCreator + public DruidKerberosConfig(@JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab) { + this.principal = principal; + this.keytab = keytab; + } + + @JsonProperty + public String getPrincipal() + { + return principal; + } + + @JsonProperty + public String getKeytab() + { + return keytab; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (!(o instanceof DruidKerberosConfig)) { + return false; + } + + DruidKerberosConfig that = (DruidKerberosConfig) o; + + if (getPrincipal() != null ? !getPrincipal().equals(that.getPrincipal()) : that.getPrincipal() != null) { + return false; + } + return getKeytab() != null ? getKeytab().equals(that.getKeytab()) : that.getKeytab() == null; + + } + + @Override + public int hashCode() + { + int result = getPrincipal() != null ? getPrincipal().hashCode() : 0; + result = 31 * result + (getKeytab() != null ? getKeytab().hashCode() : 0); + return result; + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java new file mode 100644 index 000000000000..b0e71592dcd4 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java @@ -0,0 +1,91 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.fasterxml.jackson.databind.Module; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.Inject; +import com.google.inject.multibindings.Multibinder; +import com.metamx.http.client.HttpClient; +import io.druid.guice.JsonConfigProvider; +import io.druid.guice.LazySingleton; +import io.druid.guice.annotations.Client; +import io.druid.guice.annotations.Global; +import io.druid.guice.http.DruidHttpClientConfig; +import io.druid.guice.http.HttpClientModule; +import io.druid.initialization.DruidModule; +import io.druid.server.initialization.jetty.ServletFilterHolder; + +import java.util.List; +import java.util.Properties; + +/** + */ +public class DruidKerberosModule implements DruidModule +{ + + private static final String PROPERTY_AUTH_TYPE = "druid.authentication.type"; + private static final String KERBEROS = "kerberos"; + + @Inject + private Properties props; + + @Override + public List getJacksonModules() + { + return ImmutableList.of( + ); + } + + @Override + public void configure(Binder binder) + { + if (isKerberosSecurityEnabled()) { + JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); + JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); + + Multibinder.newSetBinder(binder, ServletFilterHolder.class) + .addBinding() + .to(SpnegoFilterHolder.class); + JsonConfigProvider.bind(binder, "druid.global.http", DruidHttpClientConfig.class, Global.class); + binder.bind(HttpClient.class) + .annotatedWith(Global.class) + .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Global.class))) + .in(LazySingleton.class); + + JsonConfigProvider.bind(binder, "druid.broker.http", DruidHttpClientConfig.class, Client.class); + + binder.bind(HttpClient.class) + .annotatedWith(Client.class) + .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Client.class))) + .in(LazySingleton.class); + } + } + + private boolean isKerberosSecurityEnabled() + { + Preconditions.checkNotNull(props, "props"); + return props.getProperty(PROPERTY_AUTH_TYPE, "").equalsIgnoreCase(KERBEROS); + } + + +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java new file mode 100644 index 000000000000..4f9260422d12 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java @@ -0,0 +1,141 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.google.common.base.Strings; +import com.google.common.base.Throwables; +import com.google.common.util.concurrent.ListenableFuture; +import com.metamx.http.client.AbstractHttpClient; +import com.metamx.http.client.HttpClient; +import com.metamx.http.client.Request; +import com.metamx.http.client.response.HttpResponseHandler; +import io.druid.java.util.common.ISE; +import io.druid.java.util.common.logger.Logger; +import org.apache.commons.codec.binary.Base64; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +import org.apache.hadoop.security.UserGroupInformation; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.joda.time.Duration; + +import java.io.IOException; +import java.security.PrivilegedExceptionAction; + +public class KerberosHttpClient extends AbstractHttpClient +{ + private static final Logger log = new Logger(KerberosHttpClient.class); + + private final HttpClient delegate; + private final DruidKerberosConfig config; + private static final Base64 base64codec = new Base64(0); + + + public KerberosHttpClient(HttpClient delegate, DruidKerberosConfig config) + { + this.delegate = delegate; + this.config = config; + } + + @Override + public ListenableFuture go( + Request request, HttpResponseHandler httpResponseHandler, Duration duration + ) + { + try { + final String host = request.getUrl().getHost(); + authenticateIfRequired(); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() + { + @Override + public String run() throws Exception + { + return kerberosChallenge(host); + } + }); + request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + } + catch (Throwable e) { + Throwables.propagate(e); + } + return delegate.go(request, httpResponseHandler, duration); + } + + + public void authenticateIfRequired() + throws IOException + { + String principal = config.getPrincipal(); + String keytab = config.getKeytab(); + if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(keytab)) { + Configuration conf = new Configuration(); + conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); + UserGroupInformation.setConfiguration(conf); + if (UserGroupInformation.isSecurityEnabled()) { + try { + if (UserGroupInformation.getCurrentUser().hasKerberosCredentials() == false + || !UserGroupInformation.getCurrentUser().getUserName().equals(principal)) { + log.info("trying to authenticate user [%s] with keytab [%s]", principal, keytab); + UserGroupInformation.loginUserFromKeytab(principal, keytab); + } + } + catch (IOException e) { + throw new ISE(e, "Failed to authenticate user principal [%s] with keytab [%s]", principal, keytab); + } + } + } + } + + /** + * This method always needs to be called within a doAs block so that the client's TGT credentials + * can be read from the Subject. + * + * @return Kerberos Challenge String + * + * @throws Exception + */ + + public static String kerberosChallenge(String server) throws GSSException + { + // This Oid for Kerberos GSS-API mechanism. + Oid mechOid = new Oid("1.2.840.113554.1.2.2"); + GSSManager manager = GSSManager.getInstance(); + // GSS name for server + GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); + // Create a GSSContext for authentication with the service. + // We're passing client credentials as null since we want them to be read from the Subject. + GSSContext gssContext = + manager.createContext(serverName.canonicalize(mechOid), mechOid, null, GSSContext.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + gssContext.requestCredDeleg(true); + // Establish context + byte[] inToken = new byte[0]; + byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length); + gssContext.dispose(); + // Base64 encoded and stringified token for server + return new String(base64codec.encode(outToken)); + } + +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java new file mode 100644 index 000000000000..1235f848a261 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java @@ -0,0 +1,55 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.metamx.http.client.HttpClient; +import io.druid.guice.http.AbstractHttpClientProvider; + +public class KerberosHttpClientProvider extends AbstractHttpClientProvider +{ + private final Provider delegateProvider; + private DruidKerberosConfig config; + + public KerberosHttpClientProvider( + Provider delegateProvider + ) + { + this.delegateProvider = delegateProvider; + } + + @Inject + @Override + public void configure(Injector injector) + { + if (delegateProvider instanceof AbstractHttpClientProvider) { + ((AbstractHttpClientProvider) delegateProvider).configure(injector); + } + config = injector.getInstance(DruidKerberosConfig.class); + } + + @Override + public HttpClient get() + { + return new KerberosHttpClient(delegateProvider.get(), config); + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java new file mode 100644 index 000000000000..ca289135ea65 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java @@ -0,0 +1,124 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +public class SpnegoFilterConfig +{ + + public static final List DEFAULT_EXCLUDED_PATHS = ImmutableList.of("/status"); + + @JsonProperty + private final String principal; + + @JsonProperty + private final String keytab; + + @JsonProperty + private final String authToLocal; + + @JsonProperty + private final List excludedPaths; + + @JsonCreator + public SpnegoFilterConfig( + @JsonProperty("principal") String principal, + @JsonProperty("keytab") String keytab, + @JsonProperty("authToLocal") String authToLocal, + @JsonProperty("excludedPaths") List excludedPaths + ) + { + this.principal = principal; + this.keytab = keytab; + this.authToLocal = authToLocal == null ? "DEFAULT" : authToLocal; + this.excludedPaths = excludedPaths == null ? DEFAULT_EXCLUDED_PATHS : excludedPaths; + } + + @JsonProperty + public String getPrincipal() + { + return principal; + } + + @JsonProperty + public String getKeytab() + { + return keytab; + } + + @JsonProperty + public String getAuthToLocal() + { + return authToLocal; + } + + @JsonProperty + public List getExcludedPaths() + { + return excludedPaths; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SpnegoFilterConfig that = (SpnegoFilterConfig) o; + + if (principal != null ? !principal.equals(that.principal) : that.principal != null) { + return false; + } + if (keytab != null ? !keytab.equals(that.keytab) : that.keytab != null) { + return false; + } + return authToLocal != null ? authToLocal.equals(that.authToLocal) : that.authToLocal == null; + + } + + @Override + public int hashCode() + { + int result = principal != null ? principal.hashCode() : 0; + result = 31 * result + (keytab != null ? keytab.hashCode() : 0); + result = 31 * result + (authToLocal != null ? authToLocal.hashCode() : 0); + return result; + } + + @Override + public String toString() + { + return "SpnegoFilterConfig{" + + "principal='" + principal + '\'' + + ", keytab='" + keytab + '\'' + + ", authToLocal='" + authToLocal + '\'' + + ", excludedPaths=" + excludedPaths + + '}'; + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java new file mode 100644 index 000000000000..7e7dea56d4f7 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java @@ -0,0 +1,134 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import io.druid.guice.annotations.Self; +import io.druid.server.DruidNode; +import io.druid.server.initialization.jetty.ServletFilterHolder; +import org.apache.hadoop.security.SecurityUtil; +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +public class SpnegoFilterHolder implements ServletFilterHolder +{ + private final SpnegoFilterConfig config; + private final DruidNode node; + + @Inject + public SpnegoFilterHolder(SpnegoFilterConfig config, @Self DruidNode node) + { + this.config = config; + this.node = node; + } + + @Override + public Filter getFilter() + { + return new AuthenticationFilter() + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + ClassLoader prevLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(AuthenticationFilter.class.getClassLoader()); + super.init(filterConfig); + } + finally { + Thread.currentThread().setContextClassLoader(prevLoader); + } + } + + @Override + public void doFilter( + ServletRequest request, ServletResponse response, FilterChain filterChain + ) throws IOException, ServletException + { + String path = ((HttpServletRequest) request).getRequestURI(); + if (isExcluded(path)) { + filterChain.doFilter(request, response); + } + super.doFilter(request, response, filterChain); + } + }; + } + + private boolean isExcluded(String path) + { + for (String excluded : config.getExcludedPaths()) { + if (path.startsWith(excluded)) { + return true; + } + } + return false; + } + + @Override + public Class getFilterClass() + { + return null; + } + + @Override + public Map getInitParameters() + { + System.out.println(config); + Map params = new HashMap(); + try { + params.put( + "kerberos.principal", + SecurityUtil.getServerPrincipal(config.getPrincipal(), node.getHost()) + ); + params.put("kerberos.keytab", config.getKeytab()); + params.put(AuthenticationFilter.AUTH_TYPE, "kerberos"); + params.put("kerberos.name.rules", config.getAuthToLocal()); + } + catch (IOException e) { + Throwables.propagate(e); + } + return params; + } + + @Override + public String getPath() + { + return "/*"; + } + + @Override + public EnumSet getDispatcherType() + { + return null; + } +} diff --git a/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule b/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule new file mode 100644 index 000000000000..82acbdf6fe5c --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule @@ -0,0 +1 @@ +io.druid.kerberos.DruidKerberosModule diff --git a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java b/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java new file mode 100644 index 000000000000..8bb75ba638e1 --- /dev/null +++ b/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provides; +import io.druid.guice.ConfigModule; +import io.druid.guice.DruidGuiceExtensions; +import io.druid.guice.JsonConfigProvider; +import io.druid.guice.LazySingleton; +import io.druid.guice.PropertiesModule; +import io.druid.jackson.DefaultObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Properties; + +public class DruidKerberosConfigTest +{ + @Test + public void testserde() + { + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + binder.install(new PropertiesModule(Arrays.asList("test.runtime.properties"))); + binder.install(new ConfigModule()); + binder.install(new DruidGuiceExtensions()); + JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); + } + + @Provides + @LazySingleton + public ObjectMapper jsonMapper() + { + return new DefaultObjectMapper(); + } + } + ); + + Properties props = injector.getInstance(Properties.class); + DruidKerberosConfig config = injector.getInstance(DruidKerberosConfig.class); + + Assert.assertEquals(props.getProperty("druid.hadoop.security.kerberos.principal"), config.getPrincipal()); + Assert.assertEquals(props.getProperty("druid.hadoop.security.kerberos.keytab"), config.getKeytab()); + + + } +} diff --git a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java b/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java new file mode 100644 index 000000000000..892ce55a239e --- /dev/null +++ b/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.kerberos; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provides; +import io.druid.guice.ConfigModule; +import io.druid.guice.DruidGuiceExtensions; +import io.druid.guice.JsonConfigProvider; +import io.druid.guice.LazySingleton; +import io.druid.guice.PropertiesModule; +import io.druid.jackson.DefaultObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Properties; + +public class SpnegoFilterConfigTest +{ + @Test + public void testserde() + { + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + binder.install(new PropertiesModule(Arrays.asList("test.runtime.properties"))); + binder.install(new ConfigModule()); + binder.install(new DruidGuiceExtensions()); + JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); + } + + @Provides + @LazySingleton + public ObjectMapper jsonMapper() + { + return new DefaultObjectMapper(); + } + } + ); + + Properties props = injector.getInstance(Properties.class); + SpnegoFilterConfig config = injector.getInstance(SpnegoFilterConfig.class); + + Assert.assertEquals(props.getProperty("druid.hadoop.security.spnego.principal"), config.getPrincipal()); + Assert.assertEquals(props.getProperty("druid.hadoop.security.spnego.keytab"), config.getKeytab()); + Assert.assertEquals(props.getProperty("druid.hadoop.security.spnego.authToLocal"), config.getAuthToLocal()); + + + } +} diff --git a/extensions-core/druid-kerberos/src/test/resources/test.runtime.properties b/extensions-core/druid-kerberos/src/test/resources/test.runtime.properties new file mode 100644 index 000000000000..edcece1ffb71 --- /dev/null +++ b/extensions-core/druid-kerberos/src/test/resources/test.runtime.properties @@ -0,0 +1,7 @@ +druid.hadoop.security.kerberos.principal=testPrincipal +druid.hadoop.security.kerberos.keytab=testKeytab +druid.hadoop.security.spnego.principal=spnegoPrincipal +druid.hadoop.security.spnego.keytab=spnegoKeytab +druid.hadoop.security.spnego.authToLocal=testAuthToLocal + + diff --git a/pom.xml b/pom.xml index be94b9c89e7a..6655219b14fe 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,7 @@ extensions-core/avro-extensions extensions-core/datasketches + extensions-core/druid-kerberos extensions-core/hdfs-storage extensions-core/histogram extensions-core/stats From 83464e71152745cbac0e20a26050af7e3754f3c3 Mon Sep 17 00:00:00 2001 From: Nishant Date: Tue, 17 Jan 2017 17:54:29 +0530 Subject: [PATCH 02/13] Add docs --- .../extensions-core/druid-kerberos.md | 28 +++++++++++++++++++ docs/content/development/extensions.md | 1 + 2 files changed, 29 insertions(+) create mode 100644 docs/content/development/extensions-core/druid-kerberos.md diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md new file mode 100644 index 000000000000..c5bfc5f35afb --- /dev/null +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -0,0 +1,28 @@ +--- +layout: doc_page +--- + +# Druid-Kerberos + +Druid Extension to enable Authentication for Druid Nodes using Kerberos. +This extension adds AuthenticationFilter which is used to proect HTTP Endpoints using the simple and protected GSSAPI negotiation mechanism [SPNEGO](https://en.wikipedia.org/wiki/SPNEGO). +Make sure to [include](../../operations/including-extensions.html) `druid-kerberos` as an extension. + + +## Configuration + +|Property|Possible Values|Description|Default| +|--------|---------------|-----------|-------| +|`druid.authentication.type`|kerberos||Must be set to 'kerberos' to enable kerberos authetication.|empty| +|`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty| +|`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty| +|`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty| +|`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab|empty| +|`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT| +|`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|\["/status"]| + +As a note, it is required that the SPNego principal in use by the druid nodes must have a primary of HTTP (where Kerberos principals are of the form primary\[/instance]@REALM). +This is specified by [RFC-4559](https://tools.ietf.org/html/rfc4559) + + + diff --git a/docs/content/development/extensions.md b/docs/content/development/extensions.md index 6a2e996ffd44..45c116d22b0d 100644 --- a/docs/content/development/extensions.md +++ b/docs/content/development/extensions.md @@ -29,6 +29,7 @@ Core extensions are maintained by Druid committers. |druid-kafka-eight|Kafka ingest firehose (high level consumer) for realtime nodes.|[link](../development/extensions-core/kafka-eight-firehose.html)| |druid-kafka-extraction-namespace|Kafka-based namespaced lookup. Requires namespace lookup extension.|[link](../development/extensions-core/kafka-extraction-namespace.html)| |druid-kafka-indexing-service|Supervised exactly-once Kafka ingestion for the indexing service.|[link](../development/extensions-core/kafka-ingestion.html)| +|druid-kerberos|Kerberos authentication for druid nodes.|[link](../development/extensions-core/druid-kerberos.html)| |druid-lookups-cached-global|A module for [lookups](../querying/lookups.html) providing a jvm-global eager caching for lookups. It provides JDBC and URI implementations for fetching lookup data.|[link](../development/extensions-core/lookups-cached-global.html)| |druid-lookups-cached-single| Per lookup caching module to support the use cases where a lookup need to be isolated from the global pool of lookups |[link](../development/extensions-core/druid-lookups.html)| |druid-s3-extensions|Interfacing with data in AWS S3, and using S3 as deep storage.|[link](../development/extensions-core/s3.html)| From c33e9c265fdf7d303d6f3650e02e118ce052442c Mon Sep 17 00:00:00 2001 From: Nishant Date: Mon, 23 Jan 2017 21:53:34 +0530 Subject: [PATCH 03/13] review comments --- distribution/pom.xml | 2 +- docs/content/development/extensions-core/druid-kerberos.md | 5 +++-- extensions-core/druid-kerberos/pom.xml | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index 0c7d6a2db85b..0a832e3b1eaa 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -106,9 +106,9 @@ io.druid.extensions:postgresql-metadata-storage -c io.druid.extensions.contrib:scan-query - ${druid.distribution.pulldeps.opts} -c io.druid.extensions:druid-kerberos + ${druid.distribution.pulldeps.opts} diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index c5bfc5f35afb..3d1b058fe3a2 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -21,8 +21,9 @@ Make sure to [include](../../operations/including-extensions.html) `druid-kerber |`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT| |`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|\["/status"]| -As a note, it is required that the SPNego principal in use by the druid nodes must have a primary of HTTP (where Kerberos principals are of the form primary\[/instance]@REALM). -This is specified by [RFC-4559](https://tools.ietf.org/html/rfc4559) +As a note, it is required that the SPNego principal in use by the druid nodes must start with HTTP (This specified by [RFC-4559](https://tools.ietf.org/html/rfc4559)) and must be of the form "HTTP/_HOST@REALM". +The special string _HOST will be replaced automatically with the value of config `druid.host` + diff --git a/extensions-core/druid-kerberos/pom.xml b/extensions-core/druid-kerberos/pom.xml index ce5bcd58e5e7..74ba2b109fbc 100644 --- a/extensions-core/druid-kerberos/pom.xml +++ b/extensions-core/druid-kerberos/pom.xml @@ -177,10 +177,6 @@ easymock test - - org.eclipse.jetty - jetty-util - From fb930509f7e540873a5463140e0f59c30ef73e0e Mon Sep 17 00:00:00 2001 From: Nishant Date: Tue, 24 Jan 2017 20:26:12 +0530 Subject: [PATCH 04/13] more review comments --- .../extensions-core/druid-kerberos.md | 20 ++-- .../kerberos/DruidKerberosConfig.java | 2 +- .../kerberos/DruidKerberosModule.java | 19 +-- .../kerberos/DruidKerberosUtil.java} | 95 ++++----------- .../security/kerberos/KerberosHttpClient.java | 72 ++++++++++++ .../kerberos/KerberosHttpClientProvider.java | 2 +- .../KerberosJettyHttpClientProvider.java | 111 ++++++++++++++++++ .../kerberos/SpnegoFilterConfig.java | 2 +- .../kerberos/SpnegoFilterHolder.java | 6 +- .../io.druid.initialization.DruidModule | 2 +- .../kerberos/DruidKerberosConfigTest.java | 2 +- .../kerberos/SpnegoFilterConfigTest.java | 2 +- 12 files changed, 238 insertions(+), 97 deletions(-) rename extensions-core/druid-kerberos/src/main/java/io/druid/{ => security}/kerberos/DruidKerberosConfig.java (98%) rename extensions-core/druid-kerberos/src/main/java/io/druid/{ => security}/kerberos/DruidKerberosModule.java (83%) rename extensions-core/druid-kerberos/src/main/java/io/druid/{kerberos/KerberosHttpClient.java => security/kerberos/DruidKerberosUtil.java} (57%) create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java rename extensions-core/druid-kerberos/src/main/java/io/druid/{ => security}/kerberos/KerberosHttpClientProvider.java (97%) create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java rename extensions-core/druid-kerberos/src/main/java/io/druid/{ => security}/kerberos/SpnegoFilterConfig.java (98%) rename extensions-core/druid-kerberos/src/main/java/io/druid/{ => security}/kerberos/SpnegoFilterHolder.java (91%) rename extensions-core/druid-kerberos/src/test/java/io/druid/{ => security}/kerberos/DruidKerberosConfigTest.java (98%) rename extensions-core/druid-kerberos/src/test/java/io/druid/{ => security}/kerberos/SpnegoFilterConfigTest.java (98%) diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index 3d1b058fe3a2..123fe907dc06 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -5,21 +5,21 @@ layout: doc_page # Druid-Kerberos Druid Extension to enable Authentication for Druid Nodes using Kerberos. -This extension adds AuthenticationFilter which is used to proect HTTP Endpoints using the simple and protected GSSAPI negotiation mechanism [SPNEGO](https://en.wikipedia.org/wiki/SPNEGO). +This extension adds AuthenticationFilter which is used to protect HTTP Endpoints using the simple and protected GSSAPI negotiation mechanism [SPNEGO](https://en.wikipedia.org/wiki/SPNEGO). Make sure to [include](../../operations/including-extensions.html) `druid-kerberos` as an extension. ## Configuration -|Property|Possible Values|Description|Default| -|--------|---------------|-----------|-------| -|`druid.authentication.type`|kerberos||Must be set to 'kerberos' to enable kerberos authetication.|empty| -|`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty| -|`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty| -|`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty| -|`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab|empty| -|`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT| -|`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|\["/status"]| +|Property|Possible Values|Description|Default|required| +|--------|---------------|-----------|-------|--------| +|`druid.authentication.kerberos.enabled`|true/false||Must be set to 'true' to enable kerberos authetication.|false|Yes| +|`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty|Yes| +|`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty|Yes| +|`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty|Yes| +|`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab|empty|Yes| +|`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT|No| +|`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|\["/status"]|No| As a note, it is required that the SPNego principal in use by the druid nodes must start with HTTP (This specified by [RFC-4559](https://tools.ietf.org/html/rfc4559)) and must be of the form "HTTP/_HOST@REALM". The special string _HOST will be replaced automatically with the value of config `druid.host` diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java similarity index 98% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java index f69655f263ad..250ed6866217 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java similarity index 83% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java index b0e71592dcd4..38cb30b16990 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/DruidKerberosModule.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.fasterxml.jackson.databind.Module; import com.google.common.base.Preconditions; @@ -30,10 +30,11 @@ import io.druid.guice.LazySingleton; import io.druid.guice.annotations.Client; import io.druid.guice.annotations.Global; -import io.druid.guice.http.DruidHttpClientConfig; import io.druid.guice.http.HttpClientModule; +import io.druid.guice.http.JettyHttpClientModule; import io.druid.initialization.DruidModule; import io.druid.server.initialization.jetty.ServletFilterHolder; +import io.druid.server.router.Router; import java.util.List; import java.util.Properties; @@ -43,8 +44,7 @@ public class DruidKerberosModule implements DruidModule { - private static final String PROPERTY_AUTH_TYPE = "druid.authentication.type"; - private static final String KERBEROS = "kerberos"; + private static final String KERBEROS_ENABLED = "druid.authentication.kerberos.enabled"; @Inject private Properties props; @@ -66,25 +66,28 @@ public void configure(Binder binder) Multibinder.newSetBinder(binder, ServletFilterHolder.class) .addBinding() .to(SpnegoFilterHolder.class); - JsonConfigProvider.bind(binder, "druid.global.http", DruidHttpClientConfig.class, Global.class); + binder.bind(HttpClient.class) .annotatedWith(Global.class) .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Global.class))) .in(LazySingleton.class); - JsonConfigProvider.bind(binder, "druid.broker.http", DruidHttpClientConfig.class, Client.class); - binder.bind(HttpClient.class) .annotatedWith(Client.class) .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Client.class))) .in(LazySingleton.class); + + binder.bind(org.eclipse.jetty.client.HttpClient.class) + .annotatedWith(Router.class) + .toProvider(new KerberosJettyHttpClientProvider(new JettyHttpClientModule.HttpClientProvider(Router.class))) + .in(LazySingleton.class); } } private boolean isKerberosSecurityEnabled() { Preconditions.checkNotNull(props, "props"); - return props.getProperty(PROPERTY_AUTH_TYPE, "").equalsIgnoreCase(KERBEROS); + return Boolean.getBoolean(props.getProperty(KERBEROS_ENABLED, "false")); } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java similarity index 57% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java index 4f9260422d12..a89a0a4f9121 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClient.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java @@ -17,15 +17,9 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.google.common.base.Strings; -import com.google.common.base.Throwables; -import com.google.common.util.concurrent.ListenableFuture; -import com.metamx.http.client.AbstractHttpClient; -import com.metamx.http.client.HttpClient; -import com.metamx.http.client.Request; -import com.metamx.http.client.response.HttpResponseHandler; import io.druid.java.util.common.ISE; import io.druid.java.util.common.logger.Logger; import org.apache.commons.codec.binary.Base64; @@ -37,77 +31,15 @@ import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.joda.time.Duration; import java.io.IOException; -import java.security.PrivilegedExceptionAction; -public class KerberosHttpClient extends AbstractHttpClient +public class DruidKerberosUtil { - private static final Logger log = new Logger(KerberosHttpClient.class); + private static final Logger log = new Logger(DruidKerberosUtil.class); - private final HttpClient delegate; - private final DruidKerberosConfig config; private static final Base64 base64codec = new Base64(0); - - public KerberosHttpClient(HttpClient delegate, DruidKerberosConfig config) - { - this.delegate = delegate; - this.config = config; - } - - @Override - public ListenableFuture go( - Request request, HttpResponseHandler httpResponseHandler, Duration duration - ) - { - try { - final String host = request.getUrl().getHost(); - authenticateIfRequired(); - UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); - String challenge = currentUser.doAs(new PrivilegedExceptionAction() - { - @Override - public String run() throws Exception - { - return kerberosChallenge(host); - } - }); - request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); - } - catch (Throwable e) { - Throwables.propagate(e); - } - return delegate.go(request, httpResponseHandler, duration); - } - - - public void authenticateIfRequired() - throws IOException - { - String principal = config.getPrincipal(); - String keytab = config.getKeytab(); - if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(keytab)) { - Configuration conf = new Configuration(); - conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); - UserGroupInformation.setConfiguration(conf); - if (UserGroupInformation.isSecurityEnabled()) { - try { - if (UserGroupInformation.getCurrentUser().hasKerberosCredentials() == false - || !UserGroupInformation.getCurrentUser().getUserName().equals(principal)) { - log.info("trying to authenticate user [%s] with keytab [%s]", principal, keytab); - UserGroupInformation.loginUserFromKeytab(principal, keytab); - } - } - catch (IOException e) { - throw new ISE(e, "Failed to authenticate user principal [%s] with keytab [%s]", principal, keytab); - } - } - } - } - /** * This method always needs to be called within a doAs block so that the client's TGT credentials * can be read from the Subject. @@ -138,4 +70,25 @@ public static String kerberosChallenge(String server) throws GSSException return new String(base64codec.encode(outToken)); } + public static void authenticateIfRequired(DruidKerberosConfig config) + throws IOException + { + String principal = config.getPrincipal(); + String keytab = config.getKeytab(); + if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(keytab)) { + Configuration conf = new Configuration(); + conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); + UserGroupInformation.setConfiguration(conf); + try { + if (UserGroupInformation.getCurrentUser().hasKerberosCredentials() == false + || !UserGroupInformation.getCurrentUser().getUserName().equals(principal)) { + log.info("trying to authenticate user [%s] with keytab [%s]", principal, keytab); + UserGroupInformation.loginUserFromKeytab(principal, keytab); + } + } + catch (IOException e) { + throw new ISE(e, "Failed to authenticate user principal [%s] with keytab [%s]", principal, keytab); + } + } + } } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java new file mode 100644 index 000000000000..b140b3c8e650 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java @@ -0,0 +1,72 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.security.kerberos; + +import com.google.common.base.Throwables; +import com.google.common.util.concurrent.ListenableFuture; +import com.metamx.http.client.AbstractHttpClient; +import com.metamx.http.client.HttpClient; +import com.metamx.http.client.Request; +import com.metamx.http.client.response.HttpResponseHandler; +import io.druid.java.util.common.logger.Logger; +import org.apache.hadoop.security.UserGroupInformation; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.joda.time.Duration; + +import java.security.PrivilegedExceptionAction; + +public class KerberosHttpClient extends AbstractHttpClient +{ + private final HttpClient delegate; + private final DruidKerberosConfig config; + + + public KerberosHttpClient(HttpClient delegate, DruidKerberosConfig config) + { + this.delegate = delegate; + this.config = config; + } + + @Override + public ListenableFuture go( + Request request, HttpResponseHandler httpResponseHandler, Duration duration + ) + { + try { + final String host = request.getUrl().getHost(); + DruidKerberosUtil.authenticateIfRequired(config); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() + { + @Override + public String run() throws Exception + { + return DruidKerberosUtil.kerberosChallenge(host); + } + }); + request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + } + catch (Throwable e) { + Throwables.propagate(e); + } + return delegate.go(request, httpResponseHandler, duration); + } + +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java similarity index 97% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java index 1235f848a261..1d45d9c627f6 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/KerberosHttpClientProvider.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.google.inject.Inject; import com.google.inject.Injector; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java new file mode 100644 index 000000000000..3a3a3fcda622 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java @@ -0,0 +1,111 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.security.kerberos; + + +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import io.druid.guice.http.AbstractHttpClientProvider; +import org.apache.hadoop.security.UserGroupInformation; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.Attributes; +import org.jboss.netty.handler.codec.http.HttpHeaders; + +import java.net.URI; +import java.security.PrivilegedExceptionAction; + +public class KerberosJettyHttpClientProvider extends AbstractHttpClientProvider +{ + private final Provider delegateProvider; + private DruidKerberosConfig config; + + + public KerberosJettyHttpClientProvider( + Provider delegateProvider + ) + { + this.delegateProvider = delegateProvider; + } + + @Inject + @Override + public void configure(Injector injector) + { + config = injector.getInstance(DruidKerberosConfig.class); + } + + + @Override + public HttpClient get() + { + HttpClient httpClient = delegateProvider.get(); + httpClient.getAuthenticationStore().addAuthentication(new Authentication() + { + @Override + public boolean matches(String type, URI uri, String realm) + { + return true; + } + + @Override + public Result authenticate( + final Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context + ) + { + return new Result() + { + @Override + public URI getURI() + { + return request.getURI(); + } + + @Override + public void apply(Request request) + { + try { + final String host = request.getHost(); + DruidKerberosUtil.authenticateIfRequired(config); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() + { + @Override + public String run() throws Exception + { + return DruidKerberosUtil.kerberosChallenge(host); + } + }); + request.getHeaders().add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + } + catch (Throwable e) { + Throwables.propagate(e); + } + } + }; + } + }); + return httpClient; + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java similarity index 98% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java index ca289135ea65..9872a735eb05 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java similarity index 91% rename from extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java index 7e7dea56d4f7..7976c0fedc45 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/kerberos/SpnegoFilterHolder.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.google.common.base.Throwables; import com.google.inject.Inject; @@ -62,6 +62,9 @@ public void init(FilterConfig filterConfig) throws ServletException { ClassLoader prevLoader = Thread.currentThread().getContextClassLoader(); try { + // AuthenticationHandler is created during Authenticationfilter.init using reflection with thread context class loader. + // In case of druid since the class is actually loaded as an extension and filter init is done in main thread. + // We need to set the classloader explicitly to extension class loader. Thread.currentThread().setContextClassLoader(AuthenticationFilter.class.getClassLoader()); super.init(filterConfig); } @@ -103,7 +106,6 @@ public Class getFilterClass() @Override public Map getInitParameters() { - System.out.println(config); Map params = new HashMap(); try { params.put( diff --git a/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule b/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule index 82acbdf6fe5c..c1d9b227def1 100644 --- a/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule +++ b/extensions-core/druid-kerberos/src/main/resources/META-INF/services/io.druid.initialization.DruidModule @@ -1 +1 @@ -io.druid.kerberos.DruidKerberosModule +io.druid.security.kerberos.DruidKerberosModule diff --git a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java similarity index 98% rename from extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java rename to extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java index 8bb75ba638e1..b6c3c414a452 100644 --- a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/DruidKerberosConfigTest.java +++ b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Binder; diff --git a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/SpnegoFilterConfigTest.java similarity index 98% rename from extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java rename to extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/SpnegoFilterConfigTest.java index 892ce55a239e..1c63d09f1da9 100644 --- a/extensions-core/druid-kerberos/src/test/java/io/druid/kerberos/SpnegoFilterConfigTest.java +++ b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/SpnegoFilterConfigTest.java @@ -17,7 +17,7 @@ * under the License. */ -package io.druid.kerberos; +package io.druid.security.kerberos; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Binder; From af20be5f69f1a42440dd16fae4840ba2efea9d5f Mon Sep 17 00:00:00 2001 From: Nishant Date: Tue, 24 Jan 2017 21:57:05 +0530 Subject: [PATCH 05/13] Block all paths by default --- docs/content/development/extensions-core/druid-kerberos.md | 2 +- .../java/io/druid/security/kerberos/SpnegoFilterConfig.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index 123fe907dc06..8f961df58f49 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -19,7 +19,7 @@ Make sure to [include](../../operations/including-extensions.html) `druid-kerber |`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty|Yes| |`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab|empty|Yes| |`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT|No| -|`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|\["/status"]|No| +|`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|None|No| As a note, it is required that the SPNego principal in use by the druid nodes must start with HTTP (This specified by [RFC-4559](https://tools.ietf.org/html/rfc4559)) and must be of the form "HTTP/_HOST@REALM". The special string _HOST will be replaced automatically with the value of config `druid.host` diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java index 9872a735eb05..60567b0a2ad9 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java @@ -23,12 +23,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; +import java.util.Collections; import java.util.List; public class SpnegoFilterConfig { - public static final List DEFAULT_EXCLUDED_PATHS = ImmutableList.of("/status"); + public static final List DEFAULT_EXCLUDED_PATHS = Collections.emptyList(); @JsonProperty private final String principal; From d581b2f831b9a7b07c3009a45bcacfc016292a17 Mon Sep 17 00:00:00 2001 From: Nishant Date: Tue, 24 Jan 2017 22:31:14 +0530 Subject: [PATCH 06/13] more review comments - use proper Oid --- .../kerberos/DruidKerberosConfig.java | 3 +- .../security/kerberos/DruidKerberosUtil.java | 43 +++++++++++-------- .../security/kerberos/KerberosHttpClient.java | 1 - .../security/kerberos/SpnegoFilterConfig.java | 1 - 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java index 250ed6866217..130588df0e49 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java @@ -31,7 +31,8 @@ public class DruidKerberosConfig private final String keytab; @JsonCreator - public DruidKerberosConfig(@JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab) { + public DruidKerberosConfig(@JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab) + { this.principal = principal; this.keytab = keytab; } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java index a89a0a4f9121..f30b95b31fee 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java @@ -26,6 +26,8 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeysPublic; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; @@ -49,25 +51,30 @@ public class DruidKerberosUtil * @throws Exception */ - public static String kerberosChallenge(String server) throws GSSException + public static String kerberosChallenge(String server) throws AuthenticationException { - // This Oid for Kerberos GSS-API mechanism. - Oid mechOid = new Oid("1.2.840.113554.1.2.2"); - GSSManager manager = GSSManager.getInstance(); - // GSS name for server - GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - // Create a GSSContext for authentication with the service. - // We're passing client credentials as null since we want them to be read from the Subject. - GSSContext gssContext = - manager.createContext(serverName.canonicalize(mechOid), mechOid, null, GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - // Establish context - byte[] inToken = new byte[0]; - byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length); - gssContext.dispose(); - // Base64 encoded and stringified token for server - return new String(base64codec.encode(outToken)); + try { + // This Oid for Kerberos GSS-API mechanism. + Oid mechOid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID"); + GSSManager manager = GSSManager.getInstance(); + // GSS name for server + GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); + // Create a GSSContext for authentication with the service. + // We're passing client credentials as null since we want them to be read from the Subject. + GSSContext gssContext = + manager.createContext(serverName.canonicalize(mechOid), mechOid, null, GSSContext.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + gssContext.requestCredDeleg(true); + // Establish context + byte[] inToken = new byte[0]; + byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length); + gssContext.dispose(); + // Base64 encoded and stringified token for server + return new String(base64codec.encode(outToken)); + } + catch (GSSException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { + throw new AuthenticationException(e); + } } public static void authenticateIfRequired(DruidKerberosConfig config) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java index b140b3c8e650..5be84a548120 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java @@ -25,7 +25,6 @@ import com.metamx.http.client.HttpClient; import com.metamx.http.client.Request; import com.metamx.http.client.response.HttpResponseHandler; -import io.druid.java.util.common.logger.Logger; import org.apache.hadoop.security.UserGroupInformation; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.joda.time.Duration; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java index 60567b0a2ad9..4fa185e9a287 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.List; From af685bfcdd0cef92e33e8a4b0efb1a38ea84dc3e Mon Sep 17 00:00:00 2001 From: Nishant Date: Wed, 25 Jan 2017 15:54:28 +0530 Subject: [PATCH 07/13] Allow extensions to override httpclient for integration tests --- .../druid/testing/guice/DruidTestModule.java | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/integration-tests/src/main/java/io/druid/testing/guice/DruidTestModule.java b/integration-tests/src/main/java/io/druid/testing/guice/DruidTestModule.java index 5755161e2ed4..ee7cda2e6840 100644 --- a/integration-tests/src/main/java/io/druid/testing/guice/DruidTestModule.java +++ b/integration-tests/src/main/java/io/druid/testing/guice/DruidTestModule.java @@ -30,20 +30,16 @@ import com.metamx.emitter.service.ServiceEmitter; import com.metamx.http.client.CredentialedHttpClient; import com.metamx.http.client.HttpClient; -import com.metamx.http.client.HttpClientConfig; -import com.metamx.http.client.HttpClientInit; import com.metamx.http.client.auth.BasicCredentials; import io.druid.curator.CuratorConfig; import io.druid.guice.JsonConfigProvider; import io.druid.guice.LazySingleton; import io.druid.guice.ManageLifecycle; -import io.druid.guice.http.DruidHttpClientConfig; +import io.druid.guice.annotations.Client; import io.druid.testing.IntegrationTestingConfig; import io.druid.testing.IntegrationTestingConfigProvider; import io.druid.testing.IntegrationTestingCuratorConfig; -import javax.net.ssl.SSLContext; - /** */ public class DruidTestModule implements Module @@ -63,24 +59,16 @@ public void configure(Binder binder) @TestClient public HttpClient getHttpClient( IntegrationTestingConfig config, - DruidHttpClientConfig httpClientConfig, - Lifecycle lifecycle + Lifecycle lifecycle, + @Client HttpClient delegate ) throws Exception { - - final HttpClientConfig.Builder builder = HttpClientConfig - .builder() - .withNumConnections(httpClientConfig.getNumConnections()) - .withReadTimeout(httpClientConfig.getReadTimeout()) - .withWorkerCount(httpClientConfig.getNumMaxThreads()); - - builder.withSslContext(SSLContext.getDefault()); - HttpClient delegate = HttpClientInit.createClient(builder.build(), lifecycle); if (config.getUsername() != null) { return new CredentialedHttpClient(new BasicCredentials(config.getUsername(), config.getPassword()), delegate); + } else { + return delegate; } - return delegate; } @Provides From 063e6d7480c94230cb178014a9f2d3c10cad6dc5 Mon Sep 17 00:00:00 2001 From: Nishant Date: Wed, 25 Jan 2017 20:54:00 +0530 Subject: [PATCH 08/13] Add kerberos lock to prevent multithreaded issues. --- .../java/io/druid/security/kerberos/DruidKerberosUtil.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java index f30b95b31fee..9dbbd457d109 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java @@ -35,6 +35,7 @@ import org.ietf.jgss.Oid; import java.io.IOException; +import java.util.concurrent.locks.ReentrantLock; public class DruidKerberosUtil { @@ -42,6 +43,9 @@ public class DruidKerberosUtil private static final Base64 base64codec = new Base64(0); + // A fair reentrant lock + private static ReentrantLock kerberosLock = new ReentrantLock(true); + /** * This method always needs to be called within a doAs block so that the client's TGT credentials * can be read from the Subject. @@ -53,6 +57,7 @@ public class DruidKerberosUtil public static String kerberosChallenge(String server) throws AuthenticationException { + kerberosLock.lock(); try { // This Oid for Kerberos GSS-API mechanism. Oid mechOid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID"); @@ -74,6 +79,8 @@ public static String kerberosChallenge(String server) throws AuthenticationExcep } catch (GSSException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { throw new AuthenticationException(e); + } finally { + kerberosLock.unlock(); } } From 83edb98ed8a1556d536fe91679aa641f6c3fc7e8 Mon Sep 17 00:00:00 2001 From: Nishant Date: Wed, 25 Jan 2017 22:52:41 +0530 Subject: [PATCH 09/13] review comment - remove enabled flag and fix router injection --- .../extensions-core/druid-kerberos.md | 1 - .../security/kerberos/DruidKerberosModule.java | 18 ------------------ .../KerberosJettyHttpClientProvider.java | 3 +++ 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index 8f961df58f49..4959c6f002a6 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -13,7 +13,6 @@ Make sure to [include](../../operations/including-extensions.html) `druid-kerber |Property|Possible Values|Description|Default|required| |--------|---------------|-----------|-------|--------| -|`druid.authentication.kerberos.enabled`|true/false||Must be set to 'true' to enable kerberos authetication.|false|Yes| |`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty|Yes| |`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty|Yes| |`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty|Yes| diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java index 38cb30b16990..8f672d3ed3d5 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java @@ -20,10 +20,8 @@ package io.druid.security.kerberos; import com.fasterxml.jackson.databind.Module; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.inject.Binder; -import com.google.inject.Inject; import com.google.inject.multibindings.Multibinder; import com.metamx.http.client.HttpClient; import io.druid.guice.JsonConfigProvider; @@ -37,18 +35,12 @@ import io.druid.server.router.Router; import java.util.List; -import java.util.Properties; /** */ public class DruidKerberosModule implements DruidModule { - private static final String KERBEROS_ENABLED = "druid.authentication.kerberos.enabled"; - - @Inject - private Properties props; - @Override public List getJacksonModules() { @@ -59,7 +51,6 @@ public List getJacksonModules() @Override public void configure(Binder binder) { - if (isKerberosSecurityEnabled()) { JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); @@ -81,14 +72,5 @@ public void configure(Binder binder) .annotatedWith(Router.class) .toProvider(new KerberosJettyHttpClientProvider(new JettyHttpClientModule.HttpClientProvider(Router.class))) .in(LazySingleton.class); - } } - - private boolean isKerberosSecurityEnabled() - { - Preconditions.checkNotNull(props, "props"); - return Boolean.getBoolean(props.getProperty(KERBEROS_ENABLED, "false")); - } - - } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java index 3a3a3fcda622..e75768b5dde7 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java @@ -53,6 +53,9 @@ public KerberosJettyHttpClientProvider( @Override public void configure(Injector injector) { + if (delegateProvider instanceof AbstractHttpClientProvider) { + ((AbstractHttpClientProvider) delegateProvider).configure(injector); + } config = injector.getInstance(DruidKerberosConfig.class); } From 76ae1b66ee0363e9107f3db1adc01252ed908524 Mon Sep 17 00:00:00 2001 From: Nishant Date: Mon, 30 Jan 2017 20:21:41 +0530 Subject: [PATCH 10/13] Add Cookie Handling and more detailed docs --- .../extensions-core/druid-kerberos.md | 46 +++++++- .../kerberos/DruidKerberosModule.java | 34 +++--- .../security/kerberos/DruidKerberosUtil.java | 42 ++++++- .../security/kerberos/KerberosHttpClient.java | 103 +++++++++++++++-- .../KerberosJettyHttpClientProvider.java | 40 ++++--- .../kerberos/ResponseCookieHandler.java | 92 ++++++++++++++++ .../RetryIfUnauthorizedResponseHandler.java | 104 ++++++++++++++++++ .../kerberos/RetryResponseHolder.java | 47 ++++++++ .../security/kerberos/SpnegoFilterConfig.java | 36 +++--- .../security/kerberos/SpnegoFilterHolder.java | 3 + 10 files changed, 491 insertions(+), 56 deletions(-) create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/ResponseCookieHandler.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryIfUnauthorizedResponseHandler.java create mode 100644 extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryResponseHolder.java diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index 4959c6f002a6..6641b95efaf7 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -16,13 +16,55 @@ Make sure to [include](../../operations/including-extensions.html) `druid-kerber |`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty|Yes| |`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty|Yes| |`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty|Yes| -|`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab|empty|Yes| -|`druid.hadoop.security.spnego.authToLocal`||It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT|No| +|`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab used by druid nodes|empty|Yes| +|`druid.hadoop.security.spnego.authToLocal`|`RULE:[1:$1@$0](druid@EXAMPLE.COM)s/.*/druid DEFAULT`|It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT|No| |`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|None|No| +|`druid.hadoop.security.spnego.cookieSignatureSecret`|`secretString`| Secret used to sign authentication cookies||No| As a note, it is required that the SPNego principal in use by the druid nodes must start with HTTP (This specified by [RFC-4559](https://tools.ietf.org/html/rfc4559)) and must be of the form "HTTP/_HOST@REALM". The special string _HOST will be replaced automatically with the value of config `druid.host` +### Auth to Local Syntax +`druid.hadoop.security.spnego.authToLocal` allows you to set a general rules for mapping principal names to local user names. +The syntax for mapping rules is `RULE:\[n:string](regexp)s/pattern/replacement/g`. The integer n indicates how many components the target principal should have. If this matches, then a string will be formed from string, substituting the realm of the principal for $0 and the n‘th component of the principal for $n. e.g. if the principal was druid/admin then `\[2:$2$1suffix]` would result in the string `admindruidsuffix`. +If this string matches regexp, then the s//\[g] substitution command will be run over the string. The optional g will cause the substitution to be global over the string, instead of replacing only the first match in the string. +If required, multiple rules can be be joined by newline character and specified as a String. +## Accessing Druid HTTP end points when kerberos security is enabled +1. To access druid HTTP endpoints via curl user will need to first login using `kinit` command as follows - + ``` + kinit -k -t user@REALM.COM + ``` + +2. Once the login is successful verify that login is successful using `klist` command +3. Now you can access druid HTTP endpoints using curl command as follows - + + ``` + curl --negotiate -u:anyUser -b ~/cookies.txt -c ~/cookies.txt -X POST -H'Content-Type: application/json' + ``` + + e.g to send a query from file `query.json` to druid broker use this command - + + ``` + curl --negotiate -u:anyUser -b ~/cookies.txt -c ~/cookies.txt -X POST -H'Content-Type: application/json' http://broker-host:port/druid/v2/?pretty -d @query.json + ``` + + +## Accessing coordinator or overlord console from web browser +To access Coordinator/Overlord console from browser you will need to configure your browser for SPNego authentication as follows - + +1. Safari - No configurations required. +2. Firefox - Open firefox and follow these steps - + 1. Go to `about:config` and search for `network.negotiate-auth.trusted-uris`. + 2. Double-click and add the following values: `"http://druid-coordinator-hostname:ui-port"` and `"http://druid-overlord-hostname:port"` +3. Google Chrome - From the command line run following commands - + 1. `google-chrome --auth-server-whitelist="druid-coordinator-hostname" --auth-negotiate-delegate-whitelist="druid-coordinator-hostname"` + 2. `google-chrome --auth-server-whitelist="druid-overlord-hostname" --auth-negotiate-delegate-whitelist="druid-overlord-hostname"` +4. Internet Explorer - + 1. Configure trusted websites to include `"druid-coordinator-hostname"` and `"druid-overlord-hostname"` + 2. Allow negotiation for the UI website. + +## Sending Queries programmatically +Many HTTP client libraries, such as Apache Commons [HttpComponents](https://hc.apache.org/), already have support for performing SPNEGO authentication. You can use any of the available HTTP client library to communicate with druid cluster. diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java index 8f672d3ed3d5..c5801df43ede 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java @@ -51,26 +51,26 @@ public List getJacksonModules() @Override public void configure(Binder binder) { - JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); - JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); + JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); + JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); - Multibinder.newSetBinder(binder, ServletFilterHolder.class) - .addBinding() - .to(SpnegoFilterHolder.class); + Multibinder.newSetBinder(binder, ServletFilterHolder.class) + .addBinding() + .to(SpnegoFilterHolder.class); - binder.bind(HttpClient.class) - .annotatedWith(Global.class) - .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Global.class))) - .in(LazySingleton.class); + binder.bind(HttpClient.class) + .annotatedWith(Global.class) + .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Global.class))) + .in(LazySingleton.class); - binder.bind(HttpClient.class) - .annotatedWith(Client.class) - .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Client.class))) - .in(LazySingleton.class); + binder.bind(HttpClient.class) + .annotatedWith(Client.class) + .toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Client.class))) + .in(LazySingleton.class); - binder.bind(org.eclipse.jetty.client.HttpClient.class) - .annotatedWith(Router.class) - .toProvider(new KerberosJettyHttpClientProvider(new JettyHttpClientModule.HttpClientProvider(Router.class))) - .in(LazySingleton.class); + binder.bind(org.eclipse.jetty.client.HttpClient.class) + .annotatedWith(Router.class) + .toProvider(new KerberosJettyHttpClientProvider(new JettyHttpClientModule.HttpClientProvider(Router.class))) + .in(LazySingleton.class); } } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java index 9dbbd457d109..320ca67c3cdc 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java @@ -26,6 +26,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeysPublic; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.ietf.jgss.GSSContext; @@ -35,6 +36,10 @@ import org.ietf.jgss.Oid; import java.io.IOException; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.util.List; import java.util.concurrent.locks.ReentrantLock; public class DruidKerberosUtil @@ -79,7 +84,8 @@ public static String kerberosChallenge(String server) throws AuthenticationExcep } catch (GSSException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { throw new AuthenticationException(e); - } finally { + } + finally { kerberosLock.unlock(); } } @@ -105,4 +111,38 @@ public static void authenticateIfRequired(DruidKerberosConfig config) } } } + + public static boolean needToSendCredentials(CookieStore cookieStore, URI uri){ + return getAuthCookie(cookieStore, uri) == null; + } + + public static HttpCookie getAuthCookie(CookieStore cookieStore, URI uri) + { + if (cookieStore == null) { + return null; + } + boolean isSSL = uri.getScheme().equals("https"); + List cookies = cookieStore.getCookies(); + + for (HttpCookie c : cookies) { + // If this is a secured cookie and the current connection is non-secured, + // then, skip this cookie. We need to skip this cookie because, the cookie + // replay will not be transmitted to the server. + if (c.getSecure() && !isSSL) { + continue; + } + if (c.getName().equals(AuthenticatedURL.AUTH_COOKIE)) { + return c; + } + } + return null; + } + + public static void removeAuthCookie(CookieStore cookieStore, URI uri) + { + HttpCookie authCookie = getAuthCookie(cookieStore, uri); + if (authCookie != null) { + cookieStore.remove(uri, authCookie); + } + } } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java index 5be84a548120..bcca94fd7bc4 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java @@ -20,52 +20,135 @@ package io.druid.security.kerberos; import com.google.common.base.Throwables; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import com.metamx.http.client.AbstractHttpClient; import com.metamx.http.client.HttpClient; import com.metamx.http.client.Request; import com.metamx.http.client.response.HttpResponseHandler; +import io.druid.concurrent.Execs; +import io.druid.java.util.common.logger.Logger; import org.apache.hadoop.security.UserGroupInformation; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.joda.time.Duration; +import java.net.CookieManager; +import java.net.URI; import java.security.PrivilegedExceptionAction; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; public class KerberosHttpClient extends AbstractHttpClient { + private static final Logger log = new Logger(KerberosHttpClient.class); + private final HttpClient delegate; private final DruidKerberosConfig config; - + private final CookieManager cookieManager; + private final Executor exec = Execs.singleThreaded("test-%s"); public KerberosHttpClient(HttpClient delegate, DruidKerberosConfig config) { this.delegate = delegate; this.config = config; + this.cookieManager = new CookieManager(); } @Override public ListenableFuture go( Request request, HttpResponseHandler httpResponseHandler, Duration duration ) + { + final SettableFuture retVal = SettableFuture.create(); + inner_go(request, httpResponseHandler, duration, retVal); + return retVal; + } + + + private void inner_go( + final Request request, + final HttpResponseHandler httpResponseHandler, + final Duration duration, + final SettableFuture future + ) { try { final String host = request.getUrl().getHost(); - DruidKerberosUtil.authenticateIfRequired(config); - UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); - String challenge = currentUser.doAs(new PrivilegedExceptionAction() + final URI uri = request.getUrl().toURI(); + + + Map> cookieMap = cookieManager.get(uri, Collections.>emptyMap()); + for (Map.Entry> entry : cookieMap.entrySet()) { + request.addHeaderValues(entry.getKey(), entry.getValue()); + } + final boolean should_retry_on_unauthorized_response; + + if (DruidKerberosUtil.needToSendCredentials(cookieManager.getCookieStore(), uri)) { + // No Cookies for requested URI, authenticate user and add authentication header + log.debug( + "No Auth Cookie found for URI[%s]. Existing Cookies[%s] Authenticating... ", + uri, + cookieManager.getCookieStore().getCookies() + ); + DruidKerberosUtil.authenticateIfRequired(config); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() + { + @Override + public String run() throws Exception + { + return DruidKerberosUtil.kerberosChallenge(host); + } + }); + request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + should_retry_on_unauthorized_response = false; + } else { + should_retry_on_unauthorized_response = true; + log.debug("Found Auth Cookie found for URI[%s].", uri); + } + + ListenableFuture> internalFuture = delegate.go( + request, + new RetryIfUnauthorizedResponseHandler<>(new ResponseCookieHandler( + request.getUrl().toURI(), + cookieManager, + httpResponseHandler + )), + duration + ); + + Futures.addCallback(internalFuture, new FutureCallback>() { @Override - public String run() throws Exception + public void onSuccess(RetryResponseHolder result) + { + if (should_retry_on_unauthorized_response && result.shouldRetry()) { + log.info("Preparing for Retry"); + // remove Auth cookie + DruidKerberosUtil.removeAuthCookie(cookieManager.getCookieStore(), uri); + // clear existing cookie + request.setHeader("Cookie", ""); + inner_go(request.copy(), httpResponseHandler, duration, future); + } else { + log.info("Not retrying and returning future response"); + future.set(result.getObj()); + } + } + + @Override + public void onFailure(Throwable t) { - return DruidKerberosUtil.kerberosChallenge(host); + future.setException(t); } - }); - request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + }, exec); } catch (Throwable e) { - Throwables.propagate(e); + throw Throwables.propagate(e); } - return delegate.go(request, httpResponseHandler, duration); } } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java index e75768b5dde7..eec924e84b2c 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java @@ -25,6 +25,7 @@ import com.google.inject.Injector; import com.google.inject.Provider; import io.druid.guice.http.AbstractHttpClientProvider; +import io.druid.java.util.common.logger.Logger; import org.apache.hadoop.security.UserGroupInformation; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.Authentication; @@ -38,6 +39,8 @@ public class KerberosJettyHttpClientProvider extends AbstractHttpClientProvider { + private static final Logger log = new Logger(KerberosJettyHttpClientProvider.class); + private final Provider delegateProvider; private DruidKerberosConfig config; @@ -63,7 +66,7 @@ public void configure(Injector injector) @Override public HttpClient get() { - HttpClient httpClient = delegateProvider.get(); + final HttpClient httpClient = delegateProvider.get(); httpClient.getAuthenticationStore().addAuthentication(new Authentication() { @Override @@ -74,7 +77,7 @@ public boolean matches(String type, URI uri, String realm) @Override public Result authenticate( - final Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context + final Request request, ContentResponse response, Authentication.HeaderInfo headerInfo, Attributes context ) { return new Result() @@ -89,18 +92,29 @@ public URI getURI() public void apply(Request request) { try { - final String host = request.getHost(); - DruidKerberosUtil.authenticateIfRequired(config); - UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); - String challenge = currentUser.doAs(new PrivilegedExceptionAction() - { - @Override - public String run() throws Exception + // No need to set cookies as they are handled by Jetty Http Client itself. + URI uri = request.getURI(); + if (DruidKerberosUtil.needToSendCredentials(httpClient.getCookieStore(), uri)) { + log.debug( + "No Auth Cookie found for URI[%s]. Existing Cookies[%s] Authenticating... ", + uri, + httpClient.getCookieStore().getCookies() + ); + final String host = request.getHost(); + DruidKerberosUtil.authenticateIfRequired(config); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() { - return DruidKerberosUtil.kerberosChallenge(host); - } - }); - request.getHeaders().add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + @Override + public String run() throws Exception + { + return DruidKerberosUtil.kerberosChallenge(host); + } + }); + request.getHeaders().add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); + } else { + log.debug("Found Auth Cookie found for URI[%s].", uri); + } } catch (Throwable e) { Throwables.propagate(e); diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/ResponseCookieHandler.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/ResponseCookieHandler.java new file mode 100644 index 000000000000..4e2bed419041 --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/ResponseCookieHandler.java @@ -0,0 +1,92 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.security.kerberos; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import com.metamx.http.client.response.ClientResponse; +import com.metamx.http.client.response.HttpResponseHandler; +import io.druid.java.util.common.logger.Logger; +import org.jboss.netty.handler.codec.http.HttpChunk; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.jboss.netty.handler.codec.http.HttpResponse; + +import java.io.IOException; +import java.net.CookieManager; +import java.net.URI; +import java.util.List; + +public class ResponseCookieHandler implements HttpResponseHandler +{ + private static final Logger log = new Logger(ResponseCookieHandler.class); + + private final URI uri; + private final CookieManager manager; + private final HttpResponseHandler delegate; + + public ResponseCookieHandler(URI uri, CookieManager manager, HttpResponseHandler delegate) + { + this.uri = uri; + this.manager = manager; + this.delegate = delegate; + } + + @Override + public ClientResponse handleResponse(HttpResponse httpResponse) + { + try { + final HttpHeaders headers = httpResponse.headers(); + manager.put(uri, Maps.asMap(headers.names(), new Function>() + { + @Override + public List apply(String input) + { + return headers.getAll(input); + } + })); + } + catch (IOException e) { + log.error(e, "Error while processing Cookies from header"); + } + finally { + return delegate.handleResponse(httpResponse); + } + } + + @Override + public ClientResponse handleChunk( + ClientResponse clientResponse, HttpChunk httpChunk + ) + { + return delegate.handleChunk(clientResponse, httpChunk); + } + + @Override + public ClientResponse done(ClientResponse clientResponse) + { + return delegate.done(clientResponse); + } + + @Override + public void exceptionCaught(ClientResponse clientResponse, Throwable throwable) + { + delegate.exceptionCaught(clientResponse, throwable); + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryIfUnauthorizedResponseHandler.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryIfUnauthorizedResponseHandler.java new file mode 100644 index 000000000000..a2f960d9b5da --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryIfUnauthorizedResponseHandler.java @@ -0,0 +1,104 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.security.kerberos; + +import com.metamx.http.client.response.ClientResponse; +import com.metamx.http.client.response.HttpResponseHandler; +import io.druid.java.util.common.logger.Logger; +import org.jboss.netty.handler.codec.http.HttpChunk; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; + +public class RetryIfUnauthorizedResponseHandler + implements HttpResponseHandler, RetryResponseHolder> +{ + private static final Logger log = new Logger(RetryIfUnauthorizedResponseHandler.class); + private final HttpResponseHandler httpResponseHandler; + + + public RetryIfUnauthorizedResponseHandler( + HttpResponseHandler httpResponseHandler + ) + { + this.httpResponseHandler = httpResponseHandler; + } + + @Override + public ClientResponse> handleResponse(HttpResponse httpResponse) + { + log.debug("UnauthorizedResponseHandler - Got response status [%s]", httpResponse.getStatus()); + if (httpResponse.getStatus().equals(HttpResponseStatus.UNAUTHORIZED)) { + // Drain the buffer + httpResponse.getContent().toString(); + return ClientResponse.unfinished(RetryResponseHolder.retry()); + } else { + return wrap(httpResponseHandler.handleResponse(httpResponse)); + } + } + + @Override + public ClientResponse> handleChunk( + ClientResponse> clientResponse, HttpChunk httpChunk + ) + { + if (clientResponse.getObj().shouldRetry()) { + httpChunk.getContent().toString(); + return clientResponse; + } else { + return wrap(httpResponseHandler.handleChunk(unwrap(clientResponse), httpChunk)); + } + } + + @Override + public ClientResponse> done(ClientResponse> clientResponse) + { + if (clientResponse.getObj().shouldRetry()) { + return ClientResponse.finished(RetryResponseHolder.retry()); + } else { + return wrap(httpResponseHandler.done(unwrap(clientResponse))); + } + } + + @Override + public void exceptionCaught(ClientResponse> clientResponse, Throwable throwable) + { + httpResponseHandler.exceptionCaught(unwrap(clientResponse), throwable); + } + + private ClientResponse> wrap(ClientResponse response) + { + if (response.isFinished()) { + return ClientResponse.finished(new RetryResponseHolder(false, response.getObj())); + } else { + return ClientResponse.unfinished(new RetryResponseHolder(false, response.getObj())); + } + } + + private ClientResponse unwrap(ClientResponse> response) + { + if (response.isFinished()) { + return ClientResponse.finished(response.getObj().getObj()); + } else { + return ClientResponse.unfinished(response.getObj().getObj()); + } + } + + +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryResponseHolder.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryResponseHolder.java new file mode 100644 index 000000000000..ba6863b898dd --- /dev/null +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/RetryResponseHolder.java @@ -0,0 +1,47 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.security.kerberos; + +public class RetryResponseHolder +{ + private final boolean shouldRetry; + private final T obj; + + public RetryResponseHolder(boolean shouldRetry, T obj) + { + this.shouldRetry = shouldRetry; + this.obj = obj; + } + + public static RetryResponseHolder retry() + { + return new RetryResponseHolder(true, null); + } + + public boolean shouldRetry() + { + return shouldRetry; + } + + public T getObj() + { + return obj; + } +} diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java index 4fa185e9a287..9ce8f6ab098f 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterConfig.java @@ -42,18 +42,23 @@ public class SpnegoFilterConfig @JsonProperty private final List excludedPaths; + @JsonProperty + private final String cookieSignatureSecret; + @JsonCreator public SpnegoFilterConfig( @JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab, @JsonProperty("authToLocal") String authToLocal, - @JsonProperty("excludedPaths") List excludedPaths + @JsonProperty("excludedPaths") List excludedPaths, + @JsonProperty("cookieSignatureSecret") String cookieSignatureSecret ) { this.principal = principal; this.keytab = keytab; this.authToLocal = authToLocal == null ? "DEFAULT" : authToLocal; this.excludedPaths = excludedPaths == null ? DEFAULT_EXCLUDED_PATHS : excludedPaths; + this.cookieSignatureSecret = cookieSignatureSecret; } @JsonProperty @@ -80,6 +85,12 @@ public List getExcludedPaths() return excludedPaths; } + @JsonProperty + public String getCookieSignatureSecret() + { + return cookieSignatureSecret; + } + @Override public boolean equals(Object o) { @@ -98,7 +109,15 @@ public boolean equals(Object o) if (keytab != null ? !keytab.equals(that.keytab) : that.keytab != null) { return false; } - return authToLocal != null ? authToLocal.equals(that.authToLocal) : that.authToLocal == null; + if (authToLocal != null ? !authToLocal.equals(that.authToLocal) : that.authToLocal != null) { + return false; + } + if (excludedPaths != null ? !excludedPaths.equals(that.excludedPaths) : that.excludedPaths != null) { + return false; + } + return cookieSignatureSecret != null + ? cookieSignatureSecret.equals(that.cookieSignatureSecret) + : that.cookieSignatureSecret == null; } @@ -108,17 +127,8 @@ public int hashCode() int result = principal != null ? principal.hashCode() : 0; result = 31 * result + (keytab != null ? keytab.hashCode() : 0); result = 31 * result + (authToLocal != null ? authToLocal.hashCode() : 0); + result = 31 * result + (excludedPaths != null ? excludedPaths.hashCode() : 0); + result = 31 * result + (cookieSignatureSecret != null ? cookieSignatureSecret.hashCode() : 0); return result; } - - @Override - public String toString() - { - return "SpnegoFilterConfig{" + - "principal='" + principal + '\'' + - ", keytab='" + keytab + '\'' + - ", authToLocal='" + authToLocal + '\'' + - ", excludedPaths=" + excludedPaths + - '}'; - } } diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java index 7976c0fedc45..ab2a3b6d4f4b 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/SpnegoFilterHolder.java @@ -115,6 +115,9 @@ public Map getInitParameters() params.put("kerberos.keytab", config.getKeytab()); params.put(AuthenticationFilter.AUTH_TYPE, "kerberos"); params.put("kerberos.name.rules", config.getAuthToLocal()); + if (config.getCookieSignatureSecret() != null) { + params.put("signature.secret", config.getCookieSignatureSecret()); + } } catch (IOException e) { Throwables.propagate(e); From 3df3bce0983877d22e303bc9d7f83b9cd7959f11 Mon Sep 17 00:00:00 2001 From: Nishant Date: Wed, 1 Feb 2017 22:23:44 +0530 Subject: [PATCH 11/13] review comment - rename DruidKerberosConfig -> AuthKerberosConfig --- ...berosConfig.java => AuthenticationKerberosConfig.java} | 8 ++++---- .../io/druid/security/kerberos/DruidKerberosModule.java | 2 +- .../io/druid/security/kerberos/DruidKerberosUtil.java | 2 +- .../io/druid/security/kerberos/KerberosHttpClient.java | 4 ++-- .../security/kerberos/KerberosHttpClientProvider.java | 4 ++-- .../kerberos/KerberosJettyHttpClientProvider.java | 4 ++-- ...figTest.java => AuthenticationKerberosConfigTest.java} | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) rename extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/{DruidKerberosConfig.java => AuthenticationKerberosConfig.java} (86%) rename extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/{DruidKerberosConfigTest.java => AuthenticationKerberosConfigTest.java} (92%) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/AuthenticationKerberosConfig.java similarity index 86% rename from extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java rename to extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/AuthenticationKerberosConfig.java index 130588df0e49..7ed11fe59cc0 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosConfig.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/AuthenticationKerberosConfig.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class DruidKerberosConfig +public class AuthenticationKerberosConfig { @JsonProperty private final String principal; @@ -31,7 +31,7 @@ public class DruidKerberosConfig private final String keytab; @JsonCreator - public DruidKerberosConfig(@JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab) + public AuthenticationKerberosConfig(@JsonProperty("principal") String principal, @JsonProperty("keytab") String keytab) { this.principal = principal; this.keytab = keytab; @@ -55,11 +55,11 @@ public boolean equals(Object o) if (this == o) { return true; } - if (!(o instanceof DruidKerberosConfig)) { + if (!(o instanceof AuthenticationKerberosConfig)) { return false; } - DruidKerberosConfig that = (DruidKerberosConfig) o; + AuthenticationKerberosConfig that = (AuthenticationKerberosConfig) o; if (getPrincipal() != null ? !getPrincipal().equals(that.getPrincipal()) : that.getPrincipal() != null) { return false; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java index c5801df43ede..30d4df8b5ef9 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosModule.java @@ -51,7 +51,7 @@ public List getJacksonModules() @Override public void configure(Binder binder) { - JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); + JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", AuthenticationKerberosConfig.class); JsonConfigProvider.bind(binder, "druid.hadoop.security.spnego", SpnegoFilterConfig.class); Multibinder.newSetBinder(binder, ServletFilterHolder.class) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java index 320ca67c3cdc..d9596c8b4747 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/DruidKerberosUtil.java @@ -90,7 +90,7 @@ public static String kerberosChallenge(String server) throws AuthenticationExcep } } - public static void authenticateIfRequired(DruidKerberosConfig config) + public static void authenticateIfRequired(AuthenticationKerberosConfig config) throws IOException { String principal = config.getPrincipal(); diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java index bcca94fd7bc4..d0be66f921f6 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java @@ -47,11 +47,11 @@ public class KerberosHttpClient extends AbstractHttpClient private static final Logger log = new Logger(KerberosHttpClient.class); private final HttpClient delegate; - private final DruidKerberosConfig config; + private final AuthenticationKerberosConfig config; private final CookieManager cookieManager; private final Executor exec = Execs.singleThreaded("test-%s"); - public KerberosHttpClient(HttpClient delegate, DruidKerberosConfig config) + public KerberosHttpClient(HttpClient delegate, AuthenticationKerberosConfig config) { this.delegate = delegate; this.config = config; diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java index 1d45d9c627f6..6d4cb6234566 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClientProvider.java @@ -28,7 +28,7 @@ public class KerberosHttpClientProvider extends AbstractHttpClientProvider { private final Provider delegateProvider; - private DruidKerberosConfig config; + private AuthenticationKerberosConfig config; public KerberosHttpClientProvider( Provider delegateProvider @@ -44,7 +44,7 @@ public void configure(Injector injector) if (delegateProvider instanceof AbstractHttpClientProvider) { ((AbstractHttpClientProvider) delegateProvider).configure(injector); } - config = injector.getInstance(DruidKerberosConfig.class); + config = injector.getInstance(AuthenticationKerberosConfig.class); } @Override diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java index eec924e84b2c..cb8575bf48f6 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosJettyHttpClientProvider.java @@ -42,7 +42,7 @@ public class KerberosJettyHttpClientProvider extends AbstractHttpClientProvider< private static final Logger log = new Logger(KerberosJettyHttpClientProvider.class); private final Provider delegateProvider; - private DruidKerberosConfig config; + private AuthenticationKerberosConfig config; public KerberosJettyHttpClientProvider( @@ -59,7 +59,7 @@ public void configure(Injector injector) if (delegateProvider instanceof AbstractHttpClientProvider) { ((AbstractHttpClientProvider) delegateProvider).configure(injector); } - config = injector.getInstance(DruidKerberosConfig.class); + config = injector.getInstance(AuthenticationKerberosConfig.class); } diff --git a/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/AuthenticationKerberosConfigTest.java similarity index 92% rename from extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java rename to extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/AuthenticationKerberosConfigTest.java index b6c3c414a452..235513034464 100644 --- a/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/DruidKerberosConfigTest.java +++ b/extensions-core/druid-kerberos/src/test/java/io/druid/security/kerberos/AuthenticationKerberosConfigTest.java @@ -37,7 +37,7 @@ import java.util.Arrays; import java.util.Properties; -public class DruidKerberosConfigTest +public class AuthenticationKerberosConfigTest { @Test public void testserde() @@ -51,7 +51,7 @@ public void configure(Binder binder) binder.install(new PropertiesModule(Arrays.asList("test.runtime.properties"))); binder.install(new ConfigModule()); binder.install(new DruidGuiceExtensions()); - JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class); + JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", AuthenticationKerberosConfig.class); } @Provides @@ -64,7 +64,7 @@ public ObjectMapper jsonMapper() ); Properties props = injector.getInstance(Properties.class); - DruidKerberosConfig config = injector.getInstance(DruidKerberosConfig.class); + AuthenticationKerberosConfig config = injector.getInstance(AuthenticationKerberosConfig.class); Assert.assertEquals(props.getProperty("druid.hadoop.security.kerberos.principal"), config.getPrincipal()); Assert.assertEquals(props.getProperty("druid.hadoop.security.kerberos.keytab"), config.getKeytab()); From 56d7cb03365d0440c8f81c1c80f082b62eac89fb Mon Sep 17 00:00:00 2001 From: Nishant Date: Wed, 1 Feb 2017 23:36:04 +0530 Subject: [PATCH 12/13] review comments --- docs/content/development/extensions-core/druid-kerberos.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/development/extensions-core/druid-kerberos.md b/docs/content/development/extensions-core/druid-kerberos.md index 6641b95efaf7..44e67cce2c7b 100644 --- a/docs/content/development/extensions-core/druid-kerberos.md +++ b/docs/content/development/extensions-core/druid-kerberos.md @@ -14,18 +14,19 @@ Make sure to [include](../../operations/including-extensions.html) `druid-kerber |Property|Possible Values|Description|Default|required| |--------|---------------|-----------|-------|--------| |`druid.hadoop.security.kerberos.principal`|`druid@EXAMPLE.COM`| Principal user name, used for internal node communication|empty|Yes| -|`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.headlessUser.keytab`|Path to keytab file used for internal node communication|empty|Yes| +|`druid.hadoop.security.kerberos.keytab`|`/etc/security/keytabs/druid.keytab`|Path to keytab file used for internal node communication|empty|Yes| |`druid.hadoop.security.spnego.principal`|`HTTP/_HOST@EXAMPLE.COM`| SPNego service principal used by druid nodes|empty|Yes| |`druid.hadoop.security.spnego.keytab`|`/etc/security/keytabs/spnego.service.keytab`|SPNego service keytab used by druid nodes|empty|Yes| |`druid.hadoop.security.spnego.authToLocal`|`RULE:[1:$1@$0](druid@EXAMPLE.COM)s/.*/druid DEFAULT`|It allows you to set a general rule for mapping principal names to local user names. It will be used if there is not an explicit mapping for the principal name that is being translated.|DEFAULT|No| |`druid.hadoop.security.spnego.excludedPaths`|`['/status','/health']`| Array of HTTP paths which which does NOT need to be authenticated.|None|No| -|`druid.hadoop.security.spnego.cookieSignatureSecret`|`secretString`| Secret used to sign authentication cookies||No| +|`druid.hadoop.security.spnego.cookieSignatureSecret`|`secretString`| Secret used to sign authentication cookies. It is advisable to explicitly set it, if you have multiple druid ndoes running on same machine with different ports as the Cookie Specification does not guarantee isolation by port.||No| As a note, it is required that the SPNego principal in use by the druid nodes must start with HTTP (This specified by [RFC-4559](https://tools.ietf.org/html/rfc4559)) and must be of the form "HTTP/_HOST@REALM". The special string _HOST will be replaced automatically with the value of config `druid.host` ### Auth to Local Syntax + `druid.hadoop.security.spnego.authToLocal` allows you to set a general rules for mapping principal names to local user names. The syntax for mapping rules is `RULE:\[n:string](regexp)s/pattern/replacement/g`. The integer n indicates how many components the target principal should have. If this matches, then a string will be formed from string, substituting the realm of the principal for $0 and the n‘th component of the principal for $n. e.g. if the principal was druid/admin then `\[2:$2$1suffix]` would result in the string `admindruidsuffix`. If this string matches regexp, then the s//\[g] substitution command will be run over the string. The optional g will cause the substitution to be global over the string, instead of replacing only the first match in the string. @@ -50,7 +51,7 @@ If required, multiple rules can be be joined by newline character and specified ``` curl --negotiate -u:anyUser -b ~/cookies.txt -c ~/cookies.txt -X POST -H'Content-Type: application/json' http://broker-host:port/druid/v2/?pretty -d @query.json ``` - + Note: Above command will authenticate the user first time using SPNego negotiate mechanism and store the authentication cookie in file. For subsequent requests the cookie will be used for authentication. ## Accessing coordinator or overlord console from web browser To access Coordinator/Overlord console from browser you will need to configure your browser for SPNego authentication as follows - From 7a79d5e6f7635430611fadbd239f72e8ed6be99b Mon Sep 17 00:00:00 2001 From: Nishant Date: Thu, 2 Feb 2017 01:01:39 +0530 Subject: [PATCH 13/13] fix travis failure on jdk7 --- .../java/io/druid/security/kerberos/KerberosHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java index d0be66f921f6..4930006b181d 100644 --- a/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java +++ b/extensions-core/druid-kerberos/src/main/java/io/druid/security/kerberos/KerberosHttpClient.java @@ -113,7 +113,7 @@ public String run() throws Exception ListenableFuture> internalFuture = delegate.go( request, - new RetryIfUnauthorizedResponseHandler<>(new ResponseCookieHandler( + new RetryIfUnauthorizedResponseHandler(new ResponseCookieHandler( request.getUrl().toURI(), cookieManager, httpResponseHandler