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

Extract out environment verification logic and add optional Spring Security component #8

Merged
merged 21 commits into from
Jan 4, 2018

Conversation

dcrissman
Copy link
Member

No description provided.

Copy link
Member

@derek63 derek63 left a comment

Choose a reason for hiding this comment

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

Looks good overall, mostly naming/organizational changes, and a logic fix or two

}
}

public String getLDAPAttribute(String certificatePrincipal, String searchAttribute) throws NamingException {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe move this method to LdapRolesProvider, since it doesn't really anything to do with environment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Environment doesn't know about any of the RolesProviders, I would like to keep it that way. Environment is already acting on DNs, so I don't think it is out of line to have this method here.

this.allAccessOu = allAccessOu;
}

public void validateEnvironment(String certificatePrincipal) throws NamingException {
Copy link
Member

Choose a reason for hiding this comment

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

If we change class name to Environment, this can just be called validate(String...

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EnvironmentUtils {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just call this Environment

}

public EnvironmentUtils(String environment) {
this(environment, null);
Copy link
Member

Choose a reason for hiding this comment

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

Add Objects.notNull(environment) here, null environment not valid here too


public EnvironmentUtils(String environment, String allAccessOu) {
if (environment == null) {
throw new NullPointerException("environment cannot be null");
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just use Objects.notNull(environment) here


@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (null != envUtils) {
Copy link
Member

Choose a reason for hiding this comment

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

You also want to check if what is configured is not an empty string (i.e. ""). In the past an empty environment signified you could skip environment validation. I think that the way this is currently written, an empty environment config would still be validated (because it is not null).

We could also choose to interpret null for the environment configuration as "doesn't need to be validated", we just need to be consistent and allow for that in CertLoginLdapLoginModule (currently requires environment to be present in config) and whatever else uses this.

We could instead a method to the Environment class called requiresValidation() that returns false when the value is null or empty string, and true otherwise (I think I like this option the best). We could also check the allAccessOu value in that method too since that is another factor in determining whether or not to do validation logic.

Copy link
Member Author

Choose a reason for hiding this comment

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

You also want to check if what is configured is not an empty string (i.e. "")

Do we need to handle blank spaces also? eg. " "

We could also choose to interpret null for the environment configuration as "doesn't need to be validated"

That is exactly what CertEnvironmentVerificationFilter does, it just handles it at that layer instead of in Environment. I did it at that level because I didn't have enough context to make changes to the logic flow of CertLdapLoginModule.

If we want this to be default behaviour, then it can definitely be moved to the Environment / requiresValidation.

Copy link
Member

@derek63 derek63 left a comment

Choose a reason for hiding this comment

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

A few more minor changes, but looks good overall


private static volatile Environment envUtils;
Copy link
Member

Choose a reason for hiding this comment

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

Probably want to rename variable too to remove the Utils from the name

@derek63
Copy link
Member

derek63 commented Jan 2, 2018

I think we should modify this project to product two separate artifacts:

jboss-cert-ldap-login-module - Jboss login module and common code/libraries

spring-cert-ldap-login-module - Spring Security classes/config and common code/libraries

It feels weird to pull in a library with jboss in the name to do spring security (and vice versa).

FYI, I updated the name of this project this morning to remove "jboss" from the name since we have more than just that now

@dcrissman
Copy link
Member Author

I think we should modify this project to product two separate artifacts:

Change has been made. I plan to bump the major version when I go to release to 2.0.0

Copy link
Member Author

@dcrissman dcrissman left a comment

Choose a reason for hiding this comment

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

Aside from my comments, this is ready for another review.

<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope>
</dependency>
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 hate this block of dependencies. Is there at least a pom we could import to control these versions?

Copy link
Member Author

Choose a reason for hiding this comment

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

Even if we did an import, it would lock us to a specific version of EAP dependencies, which I am not a fan of. I would like to just delete this block and force consuming applications to deal with specifics if needed.

Copy link
Member

Choose a reason for hiding this comment

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

No idea if there is a pom to import these versions from somewhere (probably not).

Not sure if I understand how "delete this block and force consuming applications to deal with specifics if needed" would actually work since the project would no longer compile and/or generate runtime errors if you did that.

Perhaps I am not fully understanding what you're suggesting

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 got a little carried away. Looking again, it is only slf4j that could be removed as it is defined in the parent pom at version 1.7.25. The other dependencies would need to remain for exactly the reason that you stated. Sorry.

Copy link
Member Author

Choose a reason for hiding this comment

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

As for dealing with specifics, I guess my question is what version of EAP is this targeting? Different versions of EAP are going to provide different versions of these dependencies, which has the potential to cause runtime errors. I was suggesting we remove the provided scope, use the latest and greatest versions if we choose, and let consumers dependencyManage if needed, rather than trying to guess what they might need.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, you bring up a good point about the versions though...I agree the with your suggestion that removing provided and using latest and greatest is probably best bet here. I can't remember why these got marked as provided in the first place, let's make sure to test this in JBoss when it is finished to make sure removing provided doesn't cause runtime issues

Copy link
Member

Choose a reason for hiding this comment

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

Ideally this should work in either version of EAP (which I think it does currently)

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 got a little carried away.

Or did I?

commons-lang and guava are defined in the common library at versions 2.6 and 19.0 respectively. So the redefinition in jboss-cert-ldap-login-module is only to enforce a version and provided scope. Which seems ugly to me.

Copy link
Member

Choose a reason for hiding this comment

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

If we're rolling with non-provided versions, then they can/should only exist in common I would think

} else {
// fallback to getting search name from cn in certificate principle (legacy certificates)
searchName = getLDAPAttribute(certPrincipal, CN);
searchName = environment.getLDAPAttribute(certPrincipal, CN);
Copy link
Member Author

Choose a reason for hiding this comment

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

No validation happens in this case (as was the way I found it). Should it?

Copy link
Member

Choose a reason for hiding this comment

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

I think we can actually remove this whole if/else since we don't need to support legacy certificates anymore. So just:

String searchName = environment.getLDAPAttribute(certPrincipal, UID);
environment.validate(certPrincipal);

} else {
// fallback to getting search name from cn in certificate principle (legacy certificates)
searchName = getLDAPAttribute(certPrincipal, CN);
searchName = environment.getLDAPAttribute(certPrincipal, CN);
}

Collection<String> groupNames = rolesProvider.getUserRoles(searchName);
Copy link
Member Author

Choose a reason for hiding this comment

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

searchName could be null here. LdapRolesProvider asserts that it is not null and will throw a NPE if it is. Should we assume that if searchName == null that we should skip the check for the jboss implementation? The spring implementation is a little different, but if a cert cannot be found in the request, we assume that this check should be skipped.

Copy link
Member

Choose a reason for hiding this comment

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

We need to guard against an NPE, we should probably explicitly throw an not authorized exception if a UID can't be found in the principal.

If there is a value defined for environment in config, we should never skip environment validation for any request. If a user doesn't provide a certificate in the request, authentication should fail (always). In fact wouldn't it have already failed before we got to this stage?

Copy link
Member Author

Choose a reason for hiding this comment

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

If there is a value defined for environment in config, we should never skip environment validation for any request.

This is different than how the spring implementation will work. For that solution, we assume that it is possible for multiple kinds of authentication to happen independently or together. So in that case, we have to assume that if a cert is not provided, that another form of authn could be being used.

My preference would be for both implementations to logically work the same.

Copy link
Member

Choose a reason for hiding this comment

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

But isn't this condition triggered when they present a cert, but it doesn't have a UID? They would never get to this code if they hadn't already authorized with a certificate (happens before authz in login module lifecycle.

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 don't know how it works in jboss, maybe. In spring, the check is being added to the filter chain and so will always fire. So maybe that is a difference in the two libraries and just needs to be documented?

<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> <!-- TODO provided? -->
Copy link
Member Author

Choose a reason for hiding this comment

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

Should this be provided? I left it so containers could provide their own. For example, EAP (for whatever reason) provides it's own version of the apis.

Copy link
Member

Choose a reason for hiding this comment

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

Yes java.servlet should be provided

<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1</version>
<scope>provided</scope> <!-- TODO provided? -->
Copy link
Member Author

Choose a reason for hiding this comment

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

Same question here.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, provided

<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> <!-- TODO provided? -->
</dependency>
Copy link
Member Author

Choose a reason for hiding this comment

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

Should this be provided? I left it so containers could provide their own. For example, EAP (for whatever reason) provides it's own version of the apis.

Copy link
Member

Choose a reason for hiding this comment

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

The javax.servlet, commons-lang, and guava can be marked provided, the unboundid one cannot.

<jsse keystore-password="<password>" keystore-url="file://${jboss.server.config.dir}/keystore.jks" truststore-password="<password>" truststore-url="file://${jboss.server.config.dir}/truststore.jks" client-auth="true"/>
</security-domain>
</subsystem>
```
Copy link
Member

Choose a reason for hiding this comment

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

Please add some usage information here for the Spring-based client

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 will create a README.md in the spring module.

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

Copy link
Member

Choose a reason for hiding this comment

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

Please add the spring details to this file instead of having them in a separate file, so it is visible on the main page of the repository

<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

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

No idea if there is a pom to import these versions from somewhere (probably not).

Not sure if I understand how "delete this block and force consuming applications to deal with specifics if needed" would actually work since the project would no longer compile and/or generate runtime errors if you did that.

Perhaps I am not fully understanding what you're suggesting

} else {
// fallback to getting search name from cn in certificate principle (legacy certificates)
searchName = getLDAPAttribute(certPrincipal, CN);
searchName = environment.getLDAPAttribute(certPrincipal, CN);
Copy link
Member

Choose a reason for hiding this comment

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

I think we can actually remove this whole if/else since we don't need to support legacy certificates anymore. So just:

String searchName = environment.getLDAPAttribute(certPrincipal, UID);
environment.validate(certPrincipal);

} else {
// fallback to getting search name from cn in certificate principle (legacy certificates)
searchName = getLDAPAttribute(certPrincipal, CN);
searchName = environment.getLDAPAttribute(certPrincipal, CN);
}

Collection<String> groupNames = rolesProvider.getUserRoles(searchName);
Copy link
Member

Choose a reason for hiding this comment

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

We need to guard against an NPE, we should probably explicitly throw an not authorized exception if a UID can't be found in the principal.

If there is a value defined for environment in config, we should never skip environment validation for any request. If a user doesn't provide a certificate in the request, authentication should fail (always). In fact wouldn't it have already failed before we got to this stage?

<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> <!-- TODO provided? -->
</dependency>
Copy link
Member

Choose a reason for hiding this comment

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

The javax.servlet, commons-lang, and guava can be marked provided, the unboundid one cannot.

<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> <!-- TODO provided? -->
Copy link
Member

Choose a reason for hiding this comment

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

Yes java.servlet should be provided

<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1</version>
<scope>provided</scope> <!-- TODO provided? -->
Copy link
Member

Choose a reason for hiding this comment

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

Yes, provided

((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

response.setContentType("text/html");
Copy link
Member

Choose a reason for hiding this comment

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

Should this maybe be JSON instead of HTML so it is more easily parsable by clients?

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 thought about that as well. The reason I used html is to be consistent with undertow, which uses html in all of its filter level responses.

My solution is a little convoluted here, but in the spring module I overwrote this class to throw an exception instead, which is caught by yet another filter. I am working on a hopefully better solution, but it might need to be a follow up to this PR.

Copy link
Member

Choose a reason for hiding this comment

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

I hear you on consistency, I always hated how Undertow (and prior JBoss versions with tomcat) handled this, as it is painful for the clients who are expecting a JSON response

Copy link
Member Author

Choose a reason for hiding this comment

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

Or I should say that @bvulaj is working on some changes that could simplify the CertEnvironmentVerificationFilter. So maybe this could happen in a follow up PR?

Copy link
Member

Choose a reason for hiding this comment

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

This is not an essential change for this PR, as we have been living with this behavior for over three years now from JBoss, and don't get that many complaints. Good to know @bvulaj is working on more changes to the CertEnvironmentVerificationFilter, let's pick this discussion back up when those are complete.

LOGGER.debug("Attempting Environment Cert verification");
X509Certificate certChain[] = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

if ((null != certChain) && (certChain.length > 0)) {
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this line ensure that the environment validation check will never happen if the client doesn't present a certificate?

Copy link
Member Author

Choose a reason for hiding this comment

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

You are correct, that is exactly what it does. The filter itself fires every time, regardless of if a cert is provided or not. But the validation only happens if a cert is present.

I guess I think of CertEnvironmentVerificationFilter as being akin to CertLdapLoginModule. But perhaps it is more nuanced then that?

@dcrissman
Copy link
Member Author

Ready for another round of reviews.

Copy link
Member

@derek63 derek63 left a comment

Choose a reason for hiding this comment

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

Just a few minor/nitpicky things left and I think this will be ready to merge

<jsse keystore-password="<password>" keystore-url="file://${jboss.server.config.dir}/keystore.jks" truststore-password="<password>" truststore-url="file://${jboss.server.config.dir}/truststore.jks" client-auth="true"/>
</security-domain>
</subsystem>
```
Copy link
Member

Choose a reason for hiding this comment

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

Please add the spring details to this file instead of having them in a separate file, so it is visible on the main page of the repository

@@ -160,15 +152,13 @@ public void initializeRolesProvider() throws Exception {
LOGGER.debug("Certificate principal:" + certPrincipal);

//first try getting search name from uid in certificate principle (new certificates)
Copy link
Member

Choose a reason for hiding this comment

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

You can probably remove this comment now that there is only one way after removing support for legacy certificates

searchName = getLDAPAttribute(certPrincipal, CN);
String searchName = environment.getLDAPAttribute(certPrincipal, UID);
if (StringUtils.isBlank(searchName)) {
throw new LoginException("A certificate is required.");
Copy link
Member

Choose a reason for hiding this comment

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

Please update exception message to "A certificate with a UID attribute in the subject name is required."

((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

response.setContentType("text/html");
Copy link
Member

Choose a reason for hiding this comment

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

This is not an essential change for this PR, as we have been living with this behavior for over three years now from JBoss, and don't get that many complaints. Good to know @bvulaj is working on more changes to the CertEnvironmentVerificationFilter, let's pick this discussion back up when those are complete.

@@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.redhat.auth;
package org.esbtools.auth;
Copy link
Member

Choose a reason for hiding this comment

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

Oops, thanks for fixing this :-)

@@ -0,0 +1,99 @@
package org.esbtools.auth.servlet;
Copy link
Member

Choose a reason for hiding this comment

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

Hooray tests for this stuff! 👍

pom.xml Outdated
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-integ</artifactId>
<version>1.5.7</version>
<version>${slf4j.version}</version>
Copy link
Member

Choose a reason for hiding this comment

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

I think we should either update all version values to use properties or have them all inline. This one has a property and most of the others are inline (I know I am probably the one who did this originally, so sorry for getting nitpick about this just now).

@dcrissman
Copy link
Member Author

Requested changes made. Ready for another review.

Copy link
Member

@derek63 derek63 left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

@dcrissman dcrissman merged commit 259302b into esbtools:master Jan 4, 2018
@dcrissman dcrissman deleted the spring branch January 4, 2018 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants