-
Notifications
You must be signed in to change notification settings - Fork 0
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
100 using spnego similarly like in enceladus to negotiate the auth #103
Merged
TheLydonKing
merged 33 commits into
master
from
feature/100-using-spnego-similarly-like-in-enceladus-to-negotiate-the-auth
Aug 20, 2024
Merged
Changes from 6 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
88246fa
Initial Commit
TheLydonKing 664a37c
Added Kerberos to Config
TheLydonKing 7c00778
Added config to required class
TheLydonKing 69cab58
Implement Kerberos Search
TheLydonKing 0f11ae1
Added Test LdapProvider just to test kerberosLdapSearch
TheLydonKing 0b2621e
Trying to use system property sets
TheLydonKing 892d391
Simplify code for better debugging
TheLydonKing 9fb0e2b
Split up config among new files for better readability
TheLydonKing 1693d3b
Added Missing License
TheLydonKing 3ef9aad
Fixed Tests
TheLydonKing 864f79d
Fixed Tests part 2
TheLydonKing fb64065
Added Changes to Dockerfile to include krb5 and keytab
TheLydonKing d26bf94
Revert Dockerfile change
TheLydonKing 98731ac
Add Ldap required functions for future testing once Dummy Service is …
TheLydonKing 36c75f4
Fix krb5 property
TheLydonKing 0cf9586
Set AfterProperties Set for Properties to ensure KRB5 is read
TheLydonKing 9e35e25
Remove Dummy User Service and enable LdapUserDetailsService
TheLydonKing 3866b40
Minor Fix
TheLydonKing 6d6fb83
Test Just Authentication Provider Only
TheLydonKing a487173
Test Just Service Authentication Provider Only
TheLydonKing 9e864c3
Minor Change
TheLydonKing 7d8f197
Add new UserDetailsType
TheLydonKing f1e665f
Slight Cleanup of unused code
TheLydonKing e6dca19
Disable Cache
TheLydonKing 85dac05
Disable Cache attempt 2
TheLydonKing 8249377
Add UserProvider
TheLydonKing 6656966
Clean up code
TheLydonKing 821ba5a
Add Missing License
TheLydonKing 89df77b
Fix Logging
TheLydonKing fdb43a3
Add Testing
TheLydonKing a4aab77
Fix example.application.yaml
TheLydonKing be8c91b
Remove Unused Code
TheLydonKing 68d8a2b
Amend Test Account Pattern
TheLydonKing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
api/src/main/scala/za/co/absa/loginsvc/rest/provider/kerberos/KerberosLdapUserSearch.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package za.co.absa.loginsvc.rest.provider.kerberos | ||
|
||
import org.springframework.ldap.core.DirContextOperations | ||
import org.springframework.ldap.core.support.BaseLdapPathContextSource | ||
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch | ||
|
||
class KerberosLdapUserSearch (searchBase: String, searchFilter: String, contextSource: BaseLdapPathContextSource) | ||
extends FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource) { | ||
|
||
override def searchForUser(username: String): DirContextOperations = { | ||
val user = if (username.contains("@")) { | ||
username.split("@").head | ||
} else { | ||
username | ||
} | ||
dk1844 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
super.searchForUser(user) | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
.../main/scala/za/co/absa/loginsvc/rest/provider/kerberos/KerberosSPNEGOAuthentication.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package za.co.absa.loginsvc.rest.provider.kerberos | ||
|
||
import org.slf4j.LoggerFactory | ||
import org.springframework.core.io.FileSystemResource | ||
import org.springframework.ldap.core.DirContextOperations | ||
import org.springframework.ldap.core.support.BaseLdapPathContextSource | ||
import org.springframework.security.authentication.AuthenticationManager | ||
import org.springframework.security.core.{AuthenticationException, GrantedAuthority} | ||
import org.springframework.security.core.authority.{AuthorityUtils, SimpleGrantedAuthority} | ||
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider | ||
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator | ||
import org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig | ||
import org.springframework.security.kerberos.client.ldap.KerberosLdapContextSource | ||
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter | ||
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch | ||
import org.springframework.security.ldap.userdetails.{LdapAuthoritiesPopulator, LdapUserDetailsMapper, LdapUserDetailsService} | ||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler | ||
import za.co.absa.loginsvc.rest.config.auth.ActiveDirectoryLDAPConfig | ||
|
||
import java.util | ||
import javax.naming.ldap.LdapName | ||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse} | ||
import scala.collection.JavaConverters._ | ||
|
||
class KerberosSPNEGOAuthenticationProvider(activeDirectoryLDAPConfig: ActiveDirectoryLDAPConfig) { | ||
|
||
//TODO: Split into Multiple files for neater implementation | ||
private val serviceAccount = activeDirectoryLDAPConfig.serviceAccount | ||
private val kerberos = activeDirectoryLDAPConfig.enableKerberos.get | ||
private val kerberosDebug = kerberos.debug.getOrElse(false) | ||
private val logger = LoggerFactory.getLogger(classOf[KerberosSPNEGOAuthenticationProvider]) | ||
logger.debug(s"KerberosSPNEGOAuthenticationProvider init") | ||
|
||
System.setProperty("javax.net.debug", kerberosDebug.toString) | ||
System.setProperty("sun.security.krb5.debug", kerberosDebug.toString) | ||
|
||
if (kerberos.krbFileLocation.nonEmpty) { | ||
logger.info(s"Using KRB5 CONF from ${kerberos.krbFileLocation}") | ||
System.setProperty("java.security.krb5.conf", kerberos.krbFileLocation) | ||
} | ||
|
||
private def sunJaasKerberosTicketValidator(): SunJaasKerberosTicketValidator = { | ||
val ticketValidator = new SunJaasKerberosTicketValidator() | ||
ticketValidator.setServicePrincipal("[email protected]") | ||
ticketValidator.setKeyTabLocation(new FileSystemResource(kerberos.keytabFileLocation)) | ||
ticketValidator.setDebug(kerberosDebug) | ||
ticketValidator.afterPropertiesSet() | ||
ticketValidator | ||
} | ||
|
||
private def loginConfig(): SunJaasKrb5LoginConfig = { | ||
val loginConfig = new SunJaasKrb5LoginConfig() | ||
loginConfig.setServicePrincipal("[email protected]") | ||
loginConfig.setKeyTabLocation(new FileSystemResource(kerberos.keytabFileLocation)) | ||
loginConfig.setDebug(kerberosDebug) | ||
loginConfig.setIsInitiator(true) | ||
loginConfig.setUseTicketCache(false) | ||
loginConfig.afterPropertiesSet() | ||
loginConfig | ||
} | ||
|
||
private def kerberosLdapContextSource(): KerberosLdapContextSource = { | ||
val contextSource = new KerberosLdapContextSource(activeDirectoryLDAPConfig.url) | ||
contextSource.setLoginConfig(loginConfig()) | ||
contextSource.afterPropertiesSet() | ||
contextSource | ||
} | ||
|
||
private def ldapUserDetailsService(): LdapUserDetailsService = { | ||
val userSearch = new kerberosLdapSearch(activeDirectoryLDAPConfig.domain, activeDirectoryLDAPConfig.searchFilter, kerberosLdapContextSource()) | ||
val service = new LdapUserDetailsService(userSearch, new ActiveDirectoryLdapAuthoritiesPopulator) | ||
service.setUserDetailsMapper(new LdapUserDetailsMapper()) | ||
service | ||
} | ||
|
||
def kerberosServiceAuthenticationProvider(): KerberosServiceAuthenticationProvider = { | ||
val provider = new KerberosServiceAuthenticationProvider() | ||
provider.setTicketValidator(sunJaasKerberosTicketValidator()) | ||
provider.setUserDetailsService(ldapUserDetailsService()) | ||
provider | ||
} | ||
} | ||
|
||
object RestApiKerberosAuthentication { | ||
private val logger = LoggerFactory.getLogger(this.getClass) | ||
|
||
def spnegoAuthenticationProcessingFilter(authenticationManager: AuthenticationManager): SpnegoAuthenticationProcessingFilter = { | ||
val filter = new SpnegoAuthenticationProcessingFilter() | ||
filter.setAuthenticationManager(authenticationManager) | ||
filter.setSkipIfAlreadyAuthenticated(true) | ||
filter | ||
} | ||
} | ||
|
||
private class kerberosLdapSearch(searchBase: String, searchFilter: String, contextSource: BaseLdapPathContextSource) | ||
extends FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource) { | ||
override def searchForUser(username: String): DirContextOperations = { | ||
val user = if(username.contains("@")) { | ||
username.split("@").head | ||
} | ||
else { | ||
username | ||
} | ||
super.searchForUser(user) | ||
} | ||
} | ||
|
||
private class ActiveDirectoryLdapAuthoritiesPopulator() extends LdapAuthoritiesPopulator { | ||
override def getGrantedAuthorities(userData: DirContextOperations, username: String): util.Collection[_ <: GrantedAuthority] = { | ||
val groups = userData.getStringAttributes("memberof") | ||
|
||
if(groups.isEmpty) | ||
AuthorityUtils.NO_AUTHORITIES | ||
else { | ||
groups.map({group => | ||
val ldapName = new LdapName(group) | ||
val role = ldapName.getRdn(ldapName.size()-1).getValue.toString | ||
new SimpleGrantedAuthority(role) | ||
}).toList.asJava | ||
} | ||
} | ||
dk1844 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
100 changes: 100 additions & 0 deletions
100
...la/za/co/absa/loginsvc/rest/provider/kerberos/RestApiKerberosAuthenticationProvider.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package za.co.absa.loginsvc.rest.provider.kerberos | ||
|
||
import org.slf4j.LoggerFactory | ||
import org.springframework.security.authentication.{AuthenticationProvider, BadCredentialsException, UsernamePasswordAuthenticationToken} | ||
import org.springframework.security.core.Authentication | ||
import org.springframework.security.ldap.userdetails.LdapUserDetailsService | ||
import sun.security.krb5.KrbException | ||
|
||
import java.net.{SocketTimeoutException, UnknownHostException} | ||
import java.security.PrivilegedActionException | ||
import javax.security.auth.Subject | ||
import javax.security.auth.callback.{Callback, CallbackHandler, NameCallback, PasswordCallback} | ||
import javax.security.auth.login.{AppConfigurationEntry, Configuration, LoginContext, LoginException} | ||
import scala.util.control.NonFatal | ||
|
||
class RestApiKerberosAuthenticationProvider (adServer: String, searchFilter: String, baseDN: String) | ||
extends AuthenticationProvider { | ||
|
||
case class RestApiKerberosLoginResult(loginContext: LoginContext, verifiedName: String) | ||
|
||
private val logger = LoggerFactory.getLogger(this.getClass) | ||
|
||
override def authenticate(authentication: Authentication): Authentication = { | ||
val output = try { | ||
val auth = authentication.asInstanceOf[UsernamePasswordAuthenticationToken] | ||
val loginResult = login(auth.getName, auth.getCredentials.toString) | ||
val userDetailsService = getUserDetailService(loginResult.loginContext.getSubject) | ||
val userDetails = userDetailsService.loadUserByUsername(loginResult.verifiedName) | ||
loginResult.loginContext.logout() | ||
new UsernamePasswordAuthenticationToken(userDetails, auth.getCredentials, userDetails.getAuthorities) | ||
} catch { | ||
case ex: LoginException => | ||
ex.getCause match { | ||
// This is thrown when there is an issue contacting a KDC specified in krb5.conf for the realm | ||
case nestedException: SocketTimeoutException => | ||
throw new Exception(s"Timeout host: ${nestedException.getMessage}", ex) | ||
case nestedException: UnknownHostException => | ||
throw new Exception(s"Unknown authentication host: ${nestedException.getMessage}", ex) | ||
case nestedException: KrbException => | ||
throw new BadCredentialsException(s"Invalid credentials: ${nestedException.getMessage}", ex) | ||
case NonFatal(_) => | ||
throw ex | ||
} | ||
// This is thrown when there is an issue contacting an LDAP server specified in REST API configuration | ||
case ex: PrivilegedActionException => | ||
throw new Exception(ex.toString, ex) | ||
case NonFatal(ex) => | ||
throw ex | ||
} | ||
output.setDetails(authentication.getDetails) | ||
output | ||
} | ||
|
||
override def supports(authentication: Class[_]): Boolean = { | ||
classOf[UsernamePasswordAuthenticationToken].isAssignableFrom(authentication) | ||
} | ||
|
||
private def login(username: String, password: String): RestApiKerberosLoginResult = { | ||
val loginContext = new LoginContext("", null, getSpringCBHandler(username, password), getLoginConfig) // scalastyle:ignore null | ||
loginContext.login() | ||
val loggedInUser = loginContext.getSubject.getPrincipals.iterator.next.toString | ||
logger.debug(s"Logged In User: $loggedInUser") | ||
RestApiKerberosLoginResult(loginContext, loggedInUser) | ||
} | ||
|
||
private def getSpringCBHandler(username: String, password: String) = { | ||
new CallbackHandler() { | ||
def handle(callbacks: Array[Callback]): Unit = { | ||
callbacks.foreach({ | ||
case ncb: NameCallback => ncb.setName(username) | ||
case pwdcb: PasswordCallback => pwdcb.setPassword(password.toCharArray) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
private def getUserDetailService(subject: Subject) = { | ||
val contextSource = new RestApiKerberosLdapContextSource(adServer, subject) | ||
contextSource.afterPropertiesSet() | ||
val userSearch = new KerberosLdapUserSearch(baseDN, searchFilter, contextSource) | ||
new LdapUserDetailsService(userSearch, new ActiveDirectoryLdapAuthoritiesPopulator()) | ||
} | ||
|
||
private def getLoginConfig: Configuration = { | ||
import scala.collection.JavaConverters._ | ||
|
||
new Configuration { | ||
override def getAppConfigurationEntry(name: String): Array[AppConfigurationEntry] = { | ||
val opts = Map( | ||
"storeKey" -> "true", | ||
"refreshKrb5Config" -> "true", | ||
"isInitiator" -> "true", | ||
"debug" -> "true") | ||
Array(new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", | ||
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts.asJava)) | ||
} | ||
} | ||
} | ||
} | ||
|
23 changes: 23 additions & 0 deletions
23
...n/scala/za/co/absa/loginsvc/rest/provider/kerberos/RestApiKerberosLdapContextSource.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package za.co.absa.loginsvc.rest.provider.kerberos | ||
|
||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource | ||
|
||
import java.security.PrivilegedAction | ||
import javax.naming.Context | ||
import javax.naming.directory.DirContext | ||
import javax.security.auth.Subject | ||
|
||
class RestApiKerberosLdapContextSource (url: String, subject: Subject) extends DefaultSpringSecurityContextSource(url) { | ||
override def getDirContextInstance(environment: java.util.Hashtable[String, Object]): DirContext = { | ||
|
||
environment.put(Context.SECURITY_AUTHENTICATION, "GSSAPI") | ||
val sup = super.getDirContextInstance _ | ||
|
||
logger.debug(s"Trying to authenticate to LDAP as ${subject.getPrincipals}") | ||
Subject.doAs(subject, new PrivilegedAction[DirContext]() { | ||
override def run(): DirContext = { | ||
sup(environment) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 am surprised that it is necessary to force the the
Kerberos...AuthProvider
this way. Isn't this just anotherAuthenticationProvider
just likeConfigUsersAuthenticationProvider
andActiveDirectoryLDAPAuthenticationProvider
?Perhaps it could be provided the same way as those are in
AuthManagerConfig.authManager:47
?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.
So I'm forcing the Authmanager here so that I can try and debug any issues with the kerberos auth specifically. Just trying to make it easier to find the above issue that is being run into.