-
Notifications
You must be signed in to change notification settings - Fork 3
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
Conversation
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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; |
There was a problem hiding this comment.
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
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 |
Change has been made. I plan to bump the major version when I go to release to 2.0.0 |
There was a problem hiding this 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.
jboss-cert-ldap-login-module/pom.xml
Outdated
<artifactId>commons-lang</artifactId> | ||
<version>2.6</version> | ||
<scope>provided</scope> | ||
</dependency> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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? --> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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? --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, provided
org.esbtools.auth.common/pom.xml
Outdated
<artifactId>servlet-api</artifactId> | ||
<version>2.5</version> | ||
<scope>provided</scope> <!-- TODO provided? --> | ||
</dependency> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> | ||
``` |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
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
jboss-cert-ldap-login-module/pom.xml
Outdated
<artifactId>commons-lang</artifactId> | ||
<version>2.6</version> | ||
<scope>provided</scope> | ||
</dependency> |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
org.esbtools.auth.common/pom.xml
Outdated
<artifactId>servlet-api</artifactId> | ||
<version>2.5</version> | ||
<scope>provided</scope> <!-- TODO provided? --> | ||
</dependency> |
There was a problem hiding this comment.
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? --> |
There was a problem hiding this comment.
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? --> |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
Ready for another round of reviews. |
There was a problem hiding this 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> | ||
``` |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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."); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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).
Requested changes made. Ready for another review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks!
No description provided.