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

Add support framework for Kerberos Realm #31023

Merged
merged 32 commits into from
Jun 23, 2018

Conversation

bizybot
Copy link
Contributor

@bizybot bizybot commented Jun 1, 2018

This change adds the framework to support Kerberos authN in elasticsearch.
ES is the service protected by Kerberos, each ES service node will have its
own keytab. Keytab is the file with Service principal name and encrypted key.
This can be then used to validate the authenticator coming in the request.
This change only adds support for SPNEGO mechanism and uses JGSS.
JVM options -Djava.security.krb5.conf can be used to specify krb5.conf with
additional settings if required.

For Kerberos Realm,

  • KerberosRealmSettings: Captures settings required for Kerberos
    Usually keytab (stored in the config), cache settings and krb debug flag
  • KerberosAuthenticationToken: Handles extraction of token from request
    Extracts the token from request header:
    "Authorization: Negotiate "
    If any error condition occurs, throws Exception with Rest status 401
    Also adds response header "WWW-Authenticate: Negotiate"
  • KerberosTicketValidator: Used for kerberos ticket validation and
    gss context establishment.
    On service side, we need to login first, uses Jaas to complete service login.
    To avoid more file configurations, we generate the JAAS configuration with
    required modules in memory. The token extracted from authnToken is
    passed on to GSSContext which uses service credentials (keytab) to verify
    the passed token and generates output token. If GSS context is established
    it returns tuple of client-username and out token (can be empty). If out token
    is present but context is yet not established then it will return tuple with no
    username and out token. The out token needs to be returned as response
    header 401 and "WWW-Authenticate: Negotiate " for ongoing
    negotiation. This will continue till either it fails or successful authentication on
    context establishment.
  • Changes in plugin-security policy to add required permissions
    Few settings like Jaas config and kerberos keytab access requires permissions.

For testing,

  • KerberosTestCase is the base class to start/stop kdc server
    and build test settings. SimpleKdcLdapServer(sourced from Hadoop) is a wrapper around
    SimpleKdcServer(ApacheDS), which simplifies in memory testing with KDC and uses
    in memory ldap server as its backend.

This commit adds framework to support Kerberos authN in elasticsearch.
For Kerberos Realm,
KerberosRealmSettings: Captures settings required for Kerberos
KerberosAuthenticationToken: Handles extraction of token from request
KerberosTicketValidator: Used for kerberos ticket validation and
gss context establishment.
Changes in plugin-security policy to add required permissions

For testing,
KerberosTestCase is the base class to start/stop kdc server
and build test settings. MiniKdc(Hadoop) is a wrapper around
SimpleKdcServer(ApacheDS), simplifies in memory testing with KDC.
@bizybot bizybot added >feature WIP :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) v6.4.0 labels Jun 1, 2018
@bizybot bizybot requested review from tvernum and jaymode June 1, 2018 06:06
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security

The change removes MiniKdc dependency and disabling of
license check for the same.
@bizybot
Copy link
Contributor Author

bizybot commented Jun 4, 2018

@elasticmachine test this, please.

private String base64EncodedTicket;

public KerberosAuthenticationToken(final String base64EncodedToken) {
this.principalName = UNAUTHENTICATED_PRINCIPAL_NAME;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have a field that is always set to a constant value (that in itself is extremely generic)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, we do not need it. Thanks.

try {
decodedKerberosTicket = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException iae) {
rootCause = iae;
Copy link
Contributor

Choose a reason for hiding this comment

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

This flow is pretty weird - can we just handle the exception here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed this, Thanks.

throw unauthorized("invalid negotiate authentication header value, could not decode base64 token {}", rootCause,
base64EncodedToken);
}
return new KerberosAuthenticationToken(base64EncodedToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we base64 decode the token, and then throw the result away? Shouldn't this token use the byte[]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, did not realize I was again decoding in the ticket validator, good catch. Thank you.


private static ElasticsearchSecurityException unauthorized(final String message, final Throwable cause, final Object... args) {
ElasticsearchSecurityException ese = new ElasticsearchSecurityException(message, RestStatus.UNAUTHORIZED, cause, args);
ese.addHeader(WWW_AUTHENTICATE, NEGOTIATE_AUTH_HEADER);
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 right? (It might be, I just want to know that we've thought about it).
If we receive an invalid Kerberos token, we request to negotiate again - are you sure that's what we want?

Copy link
Contributor Author

@bizybot bizybot Jun 4, 2018

Choose a reason for hiding this comment

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

Yes, this is according to RFC2616 https://tools.ietf.org/html/rfc2616#section-10.4.2, though the expectation mentioned is to respond possibly with a different scheme if it was refused for presented credentials. During extraction of the token, we haven't actually tried to validate/authenticate the credentials yet but the header itself was malformed. Looked at the Tomcat Spnego Authenticator, it responds with the same scheme, so I went with the decision.
We could respond with a different scheme but not sure if we should be doing that. For example, Kibana, in this case, would respond with login page along with www-authenticate negotiate header is what I understood from the discussion here

loginContext = serviceLogin(servicePrincipalName, keyTabPath.toString(), config.settings());
// create credentials
GSSCredential serviceCreds = createCredentials(servicePrincipalName, nameType, gssManager, loginContext);
// create gss context
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't feel like these comments add anything. They're just repeating what is already clear from the method being called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, removed those. Thanks.

base64OutToken = Base64.getEncoder().encodeToString(outToken);
}
LOGGER.debug("validateTicket isGSSContextEstablished = {}, username = {}, outToken = {}", gssContext.isEstablished(),
gssContext.getSrcName().toString(), base64OutToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be a trace log.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change done, Thanks.

});
} catch (PrivilegedActionException e) {
RuntimeException rte = ExceptionsHelper.convertToRuntime((Exception) ExceptionsHelper.unwrapCause(e));
LOGGER.log(Level.DEBUG, "Could not dispose GSS Context", rte);
Copy link
Contributor

Choose a reason for hiding this comment

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

We use .debug, not .log

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change done, Thanks.

});
} catch (PrivilegedActionException e) {
RuntimeException rte = ExceptionsHelper.convertToRuntime((Exception) ExceptionsHelper.unwrapCause(e));
LOGGER.log(Level.DEBUG, "Could not close LoginContext", rte);
Copy link
Contributor

Choose a reason for hiding this comment

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

.debug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change done, Thanks.

public void describeTo(Description description) {
}
});
KerberosAuthenticationToken.extractToken(threadContext);
Copy link
Contributor

Choose a reason for hiding this comment

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

Our standard pattern is to use LucenseTestCase.expectThrows.
Is there a reason you're using a Rule instead?

Copy link
Contributor Author

@bizybot bizybot Jun 4, 2018

Choose a reason for hiding this comment

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

Addressed this. Thank you.

public void testEqualsHashCode() {
final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken("base64EncodedToken");
EqualsHashCodeTestUtils.checkEqualsAndHashCode(kerberosAuthenticationToken, (original) -> {
return new KerberosAuthenticationToken((String) original.credentials());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you include a Mutator in this test as well ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added additional checks with Mutator, Thank you.

Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

I took a first pass and left comments. Please let me know when this is ready for review again.

public static final String TYPE = "kerberos";

/**
* Kerberos Key tab for Elasticsearch HTTP Service and Kibana HTTP Service<br>
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't mention kibana here since nothing is specific to kibana

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, removed it. Thanks.

* Uses single key tab for multiple service accounts.
*/
public static final Setting<String> HTTP_SERVICE_KEYTAB_PATH =
Setting.simpleString("http.service.keytab.path", Setting.Property.NodeScope);
Copy link
Member

Choose a reason for hiding this comment

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

can we just use keytab.path?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, done. Thanks.

*/
public static final Setting<String> HTTP_SERVICE_KEYTAB_PATH =
Setting.simpleString("http.service.keytab.path", Setting.Property.NodeScope);
public static final Setting<Boolean> SETTING_KRB_DEBUG_ENABLE =
Copy link
Member

Choose a reason for hiding this comment

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

this setting should not be dynamic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding of Dynamic is it would not require a restart of the node as these settings can be changed from API. As this is debugging log to enable verbose logging on the node for jvm kerb module, we could turn on and off after capturing the logs was what I thought and made it dynamic. Could you please let me know if this is not advisable for some reason? Thanks.

Copy link
Member

Choose a reason for hiding this comment

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

There needs to be a settings update consumer to handle the update from the update settings api. There is not one in this PR and we can always make this dynamic later on if we find the need. No other realm has dynamic settings either iirc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed Dynamic, will need to explore how Dynamic works. Thanks for the pointer.

Copy link
Member

Choose a reason for hiding this comment

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

For dynamic, you need the ClusterSettings instance and have to register a settings update consumer for the setting. Take a look at IPFilter for an example of where we do this in the security code.

// Cache
private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20);
public static final Setting<TimeValue> CACHE_TTL_SETTING = Setting.timeSetting("cache.ttl", DEFAULT_TTL, Setting.Property.NodeScope);
private static final int DEFAULT_MAX_USERS = 100_000; // 100k users
Copy link
Member

Choose a reason for hiding this comment

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

can you put the private static final values together and the public ones together rather than intermixing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done thanks.

Setting.intSetting("cache.max_users", DEFAULT_MAX_USERS, Setting.Property.NodeScope);

private KerberosRealmSettings() {
/* Empty private constructor */
Copy link
Member

Choose a reason for hiding this comment

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

this comment isn't necessary and doesn't add much. Either remove it or add value by saying this class should never be instantiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it. Thanks.

* {@link InMemoryDirectoryServer}.<br>
* Starts in memory Ldap server and then uses it as backend for Kdc Server.
*/
@SuppressForbidden(reason = "Uses Apache Kdc which requires usage of java.io.File")
Copy link
Member

Choose a reason for hiding this comment

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

do we really need this? Can you point me to where this trips the forbidden check? We should try to limit the suppress forbidden annotations to the smallest amount of code possible and this is on the whole class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have moved to the methods where it is doing Path.toFile() instead of whole class. Thanks.

try {
oid = new Oid("1.3.6.1.5.5.2");
} catch (GSSException gsse) {
ExceptionsHelper.convertToRuntime(gsse);
Copy link
Member

Choose a reason for hiding this comment

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

missing throw

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this, using the constant from KerberosTicketValidator. Thanks.

try {
oldUseSubjectCredsOnlyFlag = getAndSetSystemProperty("javax.security.auth.useSubjectCredsOnly", "true");

LOGGER.info("SpnegoClient with princName : {}", userPrincipalName);
Copy link
Member

Choose a reason for hiding this comment

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

s/princName/principal name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, Thanks.

return Base64.getEncoder().encodeToString(outToken);
}

public void close() throws LoginException, GSSException, PrivilegedActionException {
Copy link
Member

Choose a reason for hiding this comment

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

should we make it implement closeable? Also mention that it should be closed in javadocs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, did that, Thanks.

Copy link
Member

Choose a reason for hiding this comment

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

javadocs are missing about needing to be closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, Thanks

}

/**
* Instead of jaas.conf, this requires refresh of {@link Configuration}.
Copy link
Member

Choose a reason for hiding this comment

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

add complete javadocs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks.

Yogesh Gaikwad added 3 commits June 8, 2018 13:34
This commit addresses review comments.
Also during testing found Arabic "ar" locale having problem
with SimpleKdcServer so added a check for it.
Removed usage of Junit rules, using expectThrows instead.
Add missing java docs.
@bizybot
Copy link
Contributor Author

bizybot commented Jun 8, 2018

@tvernum @jaymode I have addressed your review comments. I think this is ready for another review.

Important comments that I would want you guys to look at :-

  • In KerberosAuthenticationToken when throwing ElasticsearchSecurityException for invalid authz token for 'Negotiate' scheme I am setting header 'WWW-Authenticate: Negotiate'. You both had comments around it so reiterating with my reasoning here as it seems important.
    According to RFC2616, though the expectation mentioned is to respond possibly with a different scheme if it was refused for presented credentials. During extraction of the token, we haven't actually tried to validate/authenticate the credentials yet but the header itself was malformed for given scheme. Looked at the Tomcat Spnego Authenticator, it also responds with the same scheme, so I went with the decision.
    We could respond with a different scheme but not sure if we want to do that.
    It would have been good if RFC defined something that lets the client know of all supported auth schemes. Please comment with your thoughts around it if we want to do it differently.

For the comment on adding bootstrap check for Kerberos about keytab exists during startup, I will address it in the next PR.
Thank you.

Yogesh Gaikwad added 4 commits June 9, 2018 10:23
Other languages that do not work with Apache SimpleKdcServer
"ja", "th", "hi". Changing the default during testing.
KdcConfigKey does not specify locale during getProperyKey causing
problem loading ldap backend properties. This is workaround as it
writes the key in the same locale as expected for that test by
using same property key
Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

I went through it again and left more feedback. Please let me know when its ready to be reviewed again.

@@ -18,43 +17,53 @@
import java.util.Objects;

/**
* Holds on to base 64 encoded ticket, also helps extracting token from
* {@link ThreadContext}
* This class represents AuthenticationToken for Kerberos authentication using
Copy link
Member

Choose a reason for hiding this comment

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

s/represents/represents an

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, done.

* Holds on to base 64 encoded ticket, also helps extracting token from
* {@link ThreadContext}
* This class represents AuthenticationToken for Kerberos authentication using
* SPNEGO mechanism. The token stores base 64 decoded token bytes, extracted
Copy link
Member

Choose a reason for hiding this comment

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

s/SPNEGO mechanism/SPNEGO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, done.

return null;
}

if (authorizationHeader.regionMatches(IGNORE_CASE_AUTH_HEADER_MATCH, 0, NEGOTIATE_AUTH_HEADER.trim(), 0,
Copy link
Member

Choose a reason for hiding this comment

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

why do we call trim on a constant value? There should be a whitespace after Negotiate for it to be valid

Copy link
Contributor Author

@bizybot bizybot Jun 15, 2018

Choose a reason for hiding this comment

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

Yes, you are right, we need not do it. actually not sure why I did that and the check should as "(n|N)egotiate ". I have also added the change in UsernamePasswordToken to do the check as "(b|B)asic ". Corrected it. Thank you.

return null;
}

final String base64EncodedToken = authorizationHeader.substring(NEGOTIATE_AUTH_HEADER.trim().length()).trim();
Copy link
Member

Choose a reason for hiding this comment

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

same thing here with trim. We shouldn't be calling trim all the time on a constant value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, done.


@Override
public String principal() {
return "<Unauthenticated Principal Name>";
Copy link
Member

Choose a reason for hiding this comment

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

remove Name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it. Thanks.

/**
* This class is used as a Spnego client and handles spnego interactions using
* GSS context negotiation.<br>
* Not thread safe
Copy link
Member

Choose a reason for hiding this comment

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

why isn't this class thread safe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It internally uses GSSContext which maintains sequencing for avoiding replay attacks, in this class to verify mutual authentication I am keeping the context available, so it is advisable to not use this instance across threads as GSSContext also mentions this. I have added the java doc for it. I wanted to elaborate on it but forgot to add TODO. Thanks.

return Base64.getEncoder().encodeToString(outToken);
}

public void close() throws LoginException, GSSException, PrivilegedActionException {
Copy link
Member

Choose a reason for hiding this comment

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

javadocs are missing about needing to be closed

* @throws LoginException
*/
private static LoginContext loginUsingPassword(final String principal, final SecureString password) throws LoginException {
final Set<Principal> principals = new HashSet<>();
Copy link
Member

Choose a reason for hiding this comment

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

use Collections.singleton

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for these ones they were already pointed in earlier review. Thanks.

final Set<Principal> principals = new HashSet<>();
principals.add(new KerberosPrincipal(principal));

final Subject subject = new Subject(false, principals, new HashSet<Object>(), new HashSet<Object>());
Copy link
Member

Choose a reason for hiding this comment

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

use Collections.emptySet

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for these ones they were already pointed in earlier review. Thanks.


@Override
@SuppressForbidden(
reason = "For testing application provides credentials, needs sys prop javax.security.auth.useSubjectCredsOnly")
Copy link
Member

Choose a reason for hiding this comment

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

This statement is pretty vague. Is this method really only used for a single system property?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes just wanted to do it for single sys property, updated the method name and modified it to be used to set reset same property. Thanks.

Yogesh Gaikwad added 5 commits June 15, 2018 14:11
- Removed unwanted trim() and corrected the check for Negotiate
- corrected basic authorization check to be case insensitive
- Add missing documentation
- Upgrade Apache SimpleKdcServer and its dependencies
@bizybot
Copy link
Contributor Author

bizybot commented Jun 20, 2018

Hi, @tvernum @jaymode I have addressed your review comments. I think this is ready for another review. Thank you for your time.

@@ -32,8 +30,10 @@

public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
public static final String AUTH_HEADER = "Authorization";
public static final String NEGOTIATE_AUTH_HEADER = "Negotiate ";
public static final String NEGOTIATE_SCHEME_NAME = "Negotiate";
public static final String NEGOTIATE_AUTH_HEADER = NEGOTIATE_SCHEME_NAME + " ";
Copy link
Member

Choose a reason for hiding this comment

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

This isn't the header. This is the prefix for the value of the authorization header. Either rename the variable or remove it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed it. Thanks.

@@ -173,7 +173,7 @@ private static GSSCredential createCredentials(final GSSManager gssManager,

/**
* Privileged wrapper for closing GSSContext, does not throw exceptions but logs
* them as warning.
* them as debug message.
Copy link
Member

Choose a reason for hiding this comment

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

s/debug/a debug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it, Thanks.

@@ -192,7 +192,7 @@ private static void privilegedDisposeNoThrow(final GSSContext gssContext) {

/**
* Privileged wrapper for closing LoginContext, does not throw exceptions but
* logs them as warning.
* logs them as debug message.
Copy link
Member

Choose a reason for hiding this comment

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

s/debug/a debug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it, Thanks.

String retVal = null;
try {
retVal = AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {

@Override
@SuppressForbidden(
reason = "For testing application provides credentials, needs sys prop javax.security.auth.useSubjectCredsOnly")
reason = "For tests where we provide credentials, need to set reset javax.security.auth.useSubjectCredsOnly")
Copy link
Member

Choose a reason for hiding this comment

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

s/set reset/set and reset

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it, Thanks.

if (Strings.isNullOrEmpty(headerValue)) {
return null;
}
if (headerValue.regionMatches(IGNORE_CASE_AUTH_HEADER_MATCH, 0, BASIC_AUTH_PREFIX, 0,
Copy link
Member

Choose a reason for hiding this comment

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

this change should be made in a separate PR against master. It is unrelated to kerberos and technically fixes a bug but is hidden here. Lets open up a new PR for this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, just was plain lazy. I will create a bug and a PR to address this. Thanks.

/*
* arabic and other languages have problem due to handling of GeneralizedTime in
* SimpleKdcServer For more look at :
* org.apache.kerby.asn1.type.Asn1GeneralizedTime#toBytes()
Copy link
Member

Choose a reason for hiding this comment

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

There is non-standard support for UTF-8 as implemented by Microsoft. This is controlled using the sun.security.krb5.msinterop.kstring system property. This was added in JDK7 https://bugs.openjdk.java.net/browse/JDK-2182089 and will become the default in JDK 11 https://bugs.openjdk.java.net/browse/JDK-8200152. From that issue:

In fact, RFC 3961 requires it to be UTF-8 string, and MIT krb5 also uses UTF-8. It's time to change the default to UTF-8. The system property can be kept.

final KerberosAuthenticationToken kerbAuthnToken = KerberosAuthenticationToken.extractToken(authzHeader);
assertNotNull(kerbAuthnToken);
assertEquals(UNAUTHENTICATED_PRINCIPAL_NAME, kerbAuthnToken.principal());
assertTrue(kerbAuthnToken.credentials() instanceof byte[]);
Copy link
Member

Choose a reason for hiding this comment

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

this would be better as assertThat(kerbAuthnToken.credentials(), instanceOf(byte[].class)). This is because the error message will be much better than is false expected true

Yogesh Gaikwad added 5 commits June 21, 2018 07:54
- Removed basic authentication bug fix, will create a bug
  and raise PR against that.
- Corrected variable name
- Corrected statements in comments and javadoc
- Readability in tests
case insensitive handling of basic auth header
will be addressed in separate PR.
@bizybot
Copy link
Contributor Author

bizybot commented Jun 22, 2018

Hi, @jaymode I have addressed your review comments. I think this is ready for another review. Thank you for your time.

Copy link
Contributor

@tvernum tvernum left a comment

Choose a reason for hiding this comment

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

LGTM, but I left a couple of suggestions

settings.add(CACHE_TTL_SETTING);
settings.add(CACHE_MAX_USERS_SETTING);
settings.add(SETTING_KRB_DEBUG_ENABLE);
return settings;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine - and you don't need to change it, but you can use Sets.newHashSet here, which can be a little more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, have addressed this.

// authorization scheme check is case-insensitive
private static final boolean IGNORE_CASE_AUTH_HEADER_MATCH = true;

private final byte[] base64DecodedToken;
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems strange to name this variable based on the format it used to be in.
I think tokenBytes or even decodedToken would make more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed it. Thanks.


@Override
public String principal() {
return "<Unauthenticated Principal>";
Copy link
Contributor

Choose a reason for hiding this comment

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

The way this was done in SAML with "<unauthenticated-saml-user>" has proven to be confusing for users. The log messages report a token for <unauthenticated-saml-user> and they get worried that authentication failed, or something like that.

I think we're probably better of with something like "<Kerberos Token>" instead.

Perhaps we should have a separate PR to allow this to be null, for tokens that don't know their principal yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it can be confusing, I think null makes sense and then create authenticated token with correct principal once authentication is successful so we have the principal name, where required, say logging etc. I will take it up as separate PR. Thanks.

if (pve.getCause() instanceof GSSException) {
throw (GSSException) pve.getCause();
}
throw ExceptionsHelper.convertToRuntime((Exception) ExceptionsHelper.unwrapCause(pve));
Copy link
Contributor

Choose a reason for hiding this comment

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

unwrapCause doesn't do what you think it does.
You just want pve.getException()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw the implementation for unwrapCause totally different than what I assumed. As it was taking Throwable assumed must be unwrapping root cause and I can convert to runtime. Thanks. Addressed this.

return null;
});
} catch (PrivilegedActionException e) {
LOGGER.debug("Could not dispose GSS Context", (Exception) ExceptionsHelper.unwrapCause(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

Use e.getException() instead of unwrapCause

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, addressed this.

return null;
});
} catch (PrivilegedActionException e) {
LOGGER.debug("Could not close LoginContext", (Exception) ExceptionsHelper.unwrapCause(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

Use e.getException() instead of unwrapCause

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, addressed this.

* closed using {@link LoginContext#logout()} after usage.
* @throws PrivilegedActionException when privileged action threw exception
*/
private static LoginContext serviceLogin(final String keytabFilePath, final boolean krbDebug)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: You're inconsistent in whether it's a keytab or a keyTab. I have no opinion, but pick one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I see that. Some jaas properties refer it as keyTab and some places keytab. I will make it consistent.

Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

I left a suggestion. LGTM

}
}

private String base64Encode(final byte[] outToken) {
Copy link
Member

Choose a reason for hiding this comment

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

can you rename this method? This has specific behavior for an outgoing token; ie check for null/empty

@bizybot bizybot merged commit 52d7701 into elastic:feature/kerberos Jun 23, 2018
@bizybot bizybot deleted the kerberos/ticketvalidator branch June 23, 2018 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>feature :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants