Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Druid Extension to enable Authentication using Kerberos. #3853

Merged
merged 13 commits into from
Feb 2, 2017

Conversation

nishantmonu51
Copy link
Member

Druid Extension to enable Authentication for Druid Nodes using Kerberos.
It uses the simple and protected GSSAPI negotiation mechanism, SPNEGO(https://en.wikipedia.org/wiki/SPNEGO) for authentication via HTTP.
For internal node communication it also adds a wrapper around existing HTTP client to add GSSAPI authentication headers.

@nishantmonu51 nishantmonu51 added this to the 0.10.0 milestone Jan 17, 2017
@@ -105,6 +105,8 @@
<argument>-c</argument>
<argument>io.druid.extensions:postgresql-metadata-storage</argument>
<argument>${druid.distribution.pulldeps.opts}</argument>
<argument>-c</argument>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formating

|`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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i am getting what does this mean ?

<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate line 54

- This PR adds an extension for supporting druid authentication via
Kerberos.
- Working on the docs.
@nishantmonu51
Copy link
Member Author

@b-slim Thanks for the review. handled review comments and resolved conflicts.
@himanshug can you also take a look at it ?

# 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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proect -> protect


|Property|Possible Values|Description|Default|
|--------|---------------|-----------|-------|
|`druid.authentication.type`|kerberos||Must be set to 'kerberos' to enable kerberos authetication.|empty|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we expect to have another type withing the same module ? not sure if this is needed i propose to turn it into a switch to turn it on/off druid.authentication.kerberos=true by default.

|Property|Possible Values|Description|Default|
|--------|---------------|-----------|-------|
|`druid.authentication.type`|kerberos||Must be set to 'kerberos' to enable kerberos authetication.|empty|
|`druid.hadoop.security.kerberos.principal`|`[email protected]`| Principal user name, used for internal node communication|empty|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a column to clear out what is required and not ?

|`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/[email protected]`| 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|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this take a map of object ? can you give example please ?

|`druid.hadoop.security.spnego.principal`|`HTTP/[email protected]`| 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"]|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this, usually the security guys like to block every thing by default. but it is ok if you like to have it open

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed, by default all paths are authenticated now.

private static final String PROPERTY_AUTH_TYPE = "druid.authentication.type";
private static final String KERBEROS = "kerberos";

@Inject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we avoid injecting the properties ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find a trivial way to avoid it, FWIW, I referenced this from SqlModule, there also we inject props, so maybe its ok ?
If not can you suggest an alternative ?

binder.bind(HttpClient.class)
.annotatedWith(Client.class)
.toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Client.class)))
.in(LazySingleton.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made changes for Router, since it uses Jetty Http client instead of MMX, had to write another class for that,

private boolean isKerberosSecurityEnabled()
{
Preconditions.checkNotNull(props, "props");
return props.getProperty(PROPERTY_AUTH_TYPE, "").equalsIgnoreCase(KERBEROS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as i said above this is more like true/false IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to boolean flag

* under the License.
*/

package io.druid.kerberos;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit i would call it io.druid.security.kerberos

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

@Override
public String run() throws Exception
{
return kerberosChallenge(host);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure how this actually works but the question is it blocking ? can we cache the challenge key to avoid RPC comms for every call ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you have multiple calls will this generate multiple tokens ? shouldn't make sure that we have unique token in case of concurrent requests ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishantmonu51 please this is an important one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be blocking as user is already logged in using this realm.
Also checked how other projects implement this e.g -
https://github.com/prongs/apache-hive/blob/master/jdbc/src/java/org/apache/hive/jdbc/HttpKerberosRequestInterceptor.java
which has similar logic underneath.
as far as multi threading goes, Since the credentials are read from the Subject which is initiating the context. it should be safe to call this concurrently from multiple threads.

@Override
public Map<String, String> getInitParameters()
{
System.out.println(config);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed probably.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishantmonu51 this still the same.

return kerberosChallenge(host);
}
});
request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like there is no Negotiation which means that there is no spenago in the picture at all ? i am i missing something ?

Multibinder.newSetBinder(binder, ServletFilterHolder.class)
.addBinding()
.to(SpnegoFilterHolder.class);
JsonConfigProvider.bind(binder, "druid.global.http", DruidHttpClientConfig.class, Global.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, removed.

.toProvider(new KerberosHttpClientProvider(new HttpClientModule.HttpClientProvider(Global.class)))
.in(LazySingleton.class);

JsonConfigProvider.bind(binder, "druid.broker.http", DruidHttpClientConfig.class, Client.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

@himanshug
Copy link
Contributor

looks good to me besides existing comments.

{
ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AuthenticationFilter.class.getClassLoader());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this ? can we add a comments why we need to switch the classloader ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comment

Configuration conf = new Configuration();
conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
UserGroupInformation.setConfiguration(conf);
if (UserGroupInformation.isSecurityEnabled()) {
Copy link
Contributor

@b-slim b-slim Jan 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to me it is using hadoop conf files to check of kerberos is enabled. would this still work in case we are not using hadoop config with druid ?

Copy link
Contributor

@b-slim b-slim Jan 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after digging into the hadoop code seems like you are setting conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); and then testing UserGroupInformation.isSecurityEnabled()) seems like this test will always return true. ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishantmonu51t this seems to me it is using hadoop conf files to check of kerberos is enabled. would this still work in case we are not using hadoop config with druid ?

private static final String KERBEROS_ENABLED = "druid.authentication.kerberos.enabled";

@Inject
private Properties props;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i am not wrong the only reason this is needed is to read the value of druid.authentication.kerberos.enabled.
IMO this is not need since if the user chose to add this extension to the list of loaded module it means they want it activated and if they don't all they have to do is to remove it from the list. So i guess it is fair to assume that there is no need for this flag and turn it on by adding it to the list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extensions can also be loaded from classpath, so if someone adds the jar in classpath without wanting to enable authentication, It will be useful for such cases.

|`druid.hadoop.security.spnego.principal`|`HTTP/[email protected]`| 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|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought you said everything is blocked ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@b-slim
Copy link
Contributor

b-slim commented Jan 25, 2017 via email

@nishantmonu51
Copy link
Member Author

@b-slim: Nice catch, fixed the locking.

@nishantmonu51
Copy link
Member Author

@b-slim: Have added cookie handling and added more detailed docs to include curl commands and browser configs required to access coordinator and overlord console.

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' <HTTP_END_POINT>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we mention that negotiate is needed only once ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, user should always specify negotiate. Fwiw, adding this will not mean that authentication handshake will be always done.
Above curl command works like this -

  1. sends request to that to the server with the cookies for that domain if any.
  2. If the server accepts the cookie it will return a 200 OK
  3. If the cookie is found to be invalid (multiple reasons - someone restarted the server before the cookie expired, the cookie expired) It will send a response otherwise 401 Unauthorized.
  4. The client on receiving 401 now will perform the SPNego negotiate mechanism.

Will add comment about the use of cookies.

@Override
public void configure(Binder binder)
{
JsonConfigProvider.bind(binder, "druid.hadoop.security.kerberos", DruidKerberosConfig.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: so fare we have HdfsKerberosConfig and HadoopKerberosConfig can we call this one AuthKerberosConfig or any other name that reflects the scope ?

|`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]([email protected])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|<Random value>|No|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering : if we can get away with a random String why we need this config ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main reason is that the cookie specs does not guarantee isolation against port numbers.
e.g This is a sample cookie content from my host which does not have any port info-

druid-spnego-1.openstacklocal	FALSE	/druid/v2/	FALSE	0	hadoop.auth	u=druid&[email protected]&t=kerberos&e=1486006717274&s=/pK2gDY6htUnS73Bo2DrwDSTh2Y=

so if you have multiple druid nodes running on same host using same paths and different signature (generated randomly instead of being explictly set ), user will keep doing token negotiation as cookie generated by one node will not be accepted by other node. setting the cookie signature ensures that this does not occur.
Have added more clarification in docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishantmonu51 This is a very important information to add to the docs thought looks like it is mandetory to have it random if you have multiple process in the same machine.

params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
params.put("kerberos.name.rules", config.getAuthToLocal());
if (config.getCookieSignatureSecret() != null) {
params.put("signature.secret", config.getCookieSignatureSecret());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishantmonu51 not sure where the random string will be generated ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@b-slim
Copy link
Contributor

b-slim commented Feb 1, 2017

👍

@b-slim b-slim closed this Feb 1, 2017
@b-slim b-slim reopened this Feb 1, 2017
@nishantmonu51
Copy link
Member Author

@himanshug: Handled review comments from slim, Please check again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants