Skip to content

Commit

Permalink
fixup! fixup! fixup! Adding configuration for JWT (and tenants)
Browse files Browse the repository at this point in the history
Fixes #26168: Add OAuth2 Bearer token with client_credentials flow for Rudder API authentication
  • Loading branch information
fanf committed Jan 22, 2025
1 parent d3f2911 commit b52b52d
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import bootstrap.liftweb.AuthenticationMethods
import bootstrap.liftweb.RudderConfig
import bootstrap.liftweb.RudderInMemoryUserDetailsService
import bootstrap.liftweb.RudderProperties

import com.normation.errors.IOResult
import com.normation.plugins.RudderPluginModule
import com.normation.plugins.authbackends.AuthBackendsLogger
Expand Down Expand Up @@ -74,12 +73,10 @@ import com.normation.rudder.domain.logger.PluginLogger
import com.normation.rudder.facts.nodes.NodeSecurityContext
import com.normation.rudder.rest.RoleApiMapping
import com.normation.rudder.users.*

import com.normation.zio.*
import com.typesafe.config.ConfigException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse

import java.net.URI
import java.util
import org.joda.time.DateTime
Expand Down Expand Up @@ -128,6 +125,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.jwt.JwtException
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
Expand All @@ -143,9 +141,8 @@ import org.springframework.web.client.RestClientException
import org.springframework.web.client.RestOperations
import org.springframework.web.client.RestTemplate
import org.springframework.web.client.UnknownContentTypeException

import scala.jdk.CollectionConverters.*

import zio.ZIO
import zio.syntax.*

/*
Expand Down Expand Up @@ -321,10 +318,11 @@ class AuthBackendsSpringConfiguration extends ApplicationContextAware {

// Adding API authentication by OAuth2/OIDC
if (AuthBackendsConf.isJwtConfiguredByUser) {
val config = applicationContext.getBean("jwtRegistrationRepository", classOf[Option[RudderJwtRegistration]])
RudderConfig.authenticationProviders.addSpringAuthenticationProvider(
RudderJwtAuthenticationProvider.PROTOCOL_ID,
// here, we can't use applicationContext.getBean without circular reference. It will be put in Spring cache.
oauth2ApiAuthenticationProvider
oauth2ApiAuthenticationProvider(config)
)

val http = applicationContext.getBean("publicApiSecurityFilter", classOf[DefaultSecurityFilterChain])
Expand Down Expand Up @@ -374,22 +372,32 @@ class AuthBackendsSpringConfiguration extends ApplicationContextAware {
/**
* We also need to read configuration for JWT providers in rudder config files.
* The format is defined in documentation and parsed in RudderPropertyBasedJwtRegistrationDefinition
*
* For now, we are able to manage ONLY ONE registration, because SpringSecurity token decoder
* doesn't have any logic to handle more than one JWK url.
*/
@Bean def jwtRegistrationRepository: Map[String, RudderJwtRegistration] = {
@Bean def jwtRegistrationRepository: Option[RudderJwtRegistration] = {
(
for {
_ <- AuthBackendsConf.jwtRegistration.updateRegistration(RudderProperties.config)
r <- AuthBackendsConf.jwtRegistration.registrations.get
_ <- r match {
case Nil | _ :: Nil => ZIO.unit
case h :: tail =>
AuthBackendsLoggerPure.warn(
s"Warning! Rudder JWT only support one provider at a time. Only '${h._1}' will be use, '${tail.map(_._1).mkString("','")}' will be ignored."
)
}
} yield {
r.toMap
r.headOption.map(_._2)
}
).foldZIO(
err =>
(if (AuthBackendsConf.isJwtConfiguredByUser) {
AuthBackendsLoggerPure.error(err.fullMsg)
} else {
AuthBackendsLoggerPure.debug(err.fullMsg)
}) *> Map.empty[String, RudderJwtRegistration].succeed,
}) *> None.succeed,
ok => ok.succeed
).runNow
}
Expand Down Expand Up @@ -491,15 +499,21 @@ class AuthBackendsSpringConfiguration extends ApplicationContextAware {
RudderConfig.roleApiMapping
)

@Bean def oauth2ApiAuthenticationProvider: RudderJwtAuthenticationProvider = {
new RudderJwtAuthenticationProvider(
NimbusJwtDecoder
.withJwkSetUri(
"https://dev-44311217.okta.com/oauth2/ausmjd1j96GQl1W2s5d7/v1/keys"
)
.build,
rudderJwtTokenConverter
)
@Bean def oauth2ApiAuthenticationProvider(
jwtRegistrationRepository: Option[RudderJwtRegistration]
): RudderJwtAuthenticationProvider = {
val decoder = jwtRegistrationRepository match {
case Some(reg) => NimbusJwtDecoder.withJwkSetUri(reg.jwkSetUri).build
// build a noop decoder
case None =>
new JwtDecoder {
override def decode(token: String): Jwt = {
throw new JwtException("Error: no valid JWT registration is configured in rudder")
}
}
}

new RudderJwtAuthenticationProvider(decoder, rudderJwtTokenConverter)
}

}
Expand Down Expand Up @@ -1135,7 +1149,7 @@ object RudderJwtAuthenticationConverter {
}

class RudderJwtAuthenticationConverter(
clientRegistrationRepository: Map[String, RudderJwtRegistration],
clientRegistrationRepository: Option[RudderJwtRegistration],
roleApiMapping: RoleApiMapping
) extends Converter[Jwt, AbstractAuthenticationToken] {

Expand All @@ -1155,12 +1169,12 @@ class RudderJwtAuthenticationConverter(
)
} else {

clientRegistrationRepository.find { case (_, r) => r.clientId == clientId } match {
case None =>
clientRegistrationRepository match {
case None =>
throw new InvalidBearerTokenException(
s"A JWT Bearer token was received but we don't have any registration for the provided clientId: ${clientId}"
)
case Some((rid, registration)) =>
case Some(registration) =>
// check that audience matches the expected one if the registration enforce it
if (registration.audience.check && !t.getToken.getAudience.asScala.contains(registration.audience.value)) {
throw new InvalidBearerTokenException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ final case class JwtAudience(
// properties common to both JWT and OAuth2/OIDC registration
sealed trait RudderOAuth2Registration {
def registrationId: String
def clientId: String
def roles: ProvidedRoles
def tenants: ProvidedTenants
}
Expand All @@ -129,7 +128,6 @@ sealed trait RudderOAuth2Registration {
*/
final case class RudderJwtRegistration(
registrationId: String,
clientId: String,
jwkSetUri: String,
audience: JwtAudience,
roles: ProvidedRoles,
Expand All @@ -151,7 +149,6 @@ final case class RudderClientRegistration(
tenants: ProvidedTenants
) extends RudderOAuth2Registration {
override def registrationId: String = registration.getRegistrationId
override def clientId: String = registration.getClientId

override def toString: String = {
toDebugStringWithSecret.replaceFirst("""clientSecret='([^']+?)'""", "clientSecret='*****'")
Expand Down Expand Up @@ -486,7 +483,6 @@ class RudderPropertyBasedJwtRegistrationDefinition(val registrations: Ref[List[(
implicit val c = config

for {
clientId <- read(A_CLIENT_ID)
jwkSetUri <- read(A_URI_JWK_SET)
checkAudience <- read(A_AUDIENCE_CHECK).catchAll(_ => "true".succeed)
audienceValue <- read(A_AUDIENCE_VALUE).catchAll(_ => "io.rudder.api".succeed)
Expand All @@ -495,7 +491,6 @@ class RudderPropertyBasedJwtRegistrationDefinition(val registrations: Ref[List[(
} yield {
RudderJwtRegistration(
id,
clientId,
jwkSetUri,
JwtAudience(toBool(checkAudience), audienceValue),
roles,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
rudder.auth.jwt.provider.registrations=someidp
rudder.auth.jwt.provider.someidp.name=Some ID
rudder.auth.jwt.provider.someidp.client.id=xxxClientIdxxx
rudder.auth.jwt.provider.someidp.uri.jwkSet="https://someidp/oauth2/v1/keys"
rudder.auth.jwt.provider.someidp.audience=io.rudder.api
rudder.auth.jwt.provider.someidp.roles.attribute=customroles
Expand Down
1 change: 0 additions & 1 deletion auth-backends/src/test/resources/jwt/jwt_simple.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
rudder.auth.jwt.provider.registrations=someidp
rudder.auth.jwt.provider.someidp.name=Some ID
rudder.auth.jwt.provider.someidp.client.id=xxxClientIdxxx
rudder.auth.jwt.provider.someidp.uri.jwkSet="https://someidp/oauth2/v1/keys"
rudder.auth.jwt.provider.someidp.audience=io.rudder.api
rudder.auth.jwt.provider.someidp.roles.attribute=customroles
Expand Down

0 comments on commit b52b52d

Please sign in to comment.