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

[WIP] authenticated microservices #1238

Draft
wants to merge 90 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
8009db2
Improvements for Gradle build
petertrr Sep 15, 2022
d75fe3c
Switch `spring-cloud-kubernetes` implementation from `kubernetes-clie…
petertrr Sep 16, 2022
8796ed7
[skip ci] [WIP] App-to-app authentication using ServiceAccount tokens
petertrr Sep 16, 2022
3c4d60a
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Sep 29, 2022
90c27ca
[skip ci] WIP: authenticated microservices
petertrr Sep 29, 2022
b501eda
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 5, 2022
12f01d9
[skip ci] Extract sa-token mount into _helpers.tpl; fix path to the m…
petertrr Oct 5, 2022
bb7f1de
Read ConfigMap using Kubernetes fabric8 client
petertrr Oct 7, 2022
8e27f7d
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 7, 2022
6121181
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 11, 2022
2b0900d
[skip ci] Extract the WebClientCustomizer into save-cloud-common, plu…
petertrr Oct 11, 2022
415c4f6
[skip ci] Cleanup
petertrr Oct 11, 2022
1f0e8f8
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 14, 2022
09df217
[Helm] Set another port as a management port for all JVM services
petertrr Oct 14, 2022
c235283
[skip ci] WIP
petertrr Oct 14, 2022
5c12d11
Enable `@ConditionalOnCloudPlatform` for web client customizer bean
petertrr Oct 20, 2022
49a25a3
Handle error status code when uploading test suite source snapshot fr…
petertrr Oct 20, 2022
1de5b2f
Disable CSRF so that everything works
petertrr Oct 20, 2022
86dd0a9
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 20, 2022
e390516
[skip ci] Move KubernetesAuthenticationUtils to save-cloud-common
petertrr Oct 20, 2022
a612f55
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 20, 2022
148e053
[skip ci]
petertrr Oct 20, 2022
f9c0984
Minor refactoring of WebClientCustomizers
petertrr Oct 20, 2022
6f33194
Extension method for KubernetesAuthenticationUtils
petertrr Oct 20, 2022
23bead9
Code style
petertrr Oct 20, 2022
bc037cc
Merge branch 'master' into feature/authenticated-microservices
petertrr Oct 20, 2022
6b048b6
Apply `WebClientCustomizers` to all `WebClient`s; fix `!kubernetes` s…
petertrr Oct 21, 2022
bdf937a
Protect orchestrator and sandbox with SA Token Authorization
petertrr Oct 21, 2022
698e2a5
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 21, 2022
2579328
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Oct 24, 2022
20c7171
Code style
petertrr Oct 24, 2022
fdac9db
Merge branch 'master' into feature/authenticated-microservices
petertrr Oct 26, 2022
b69b3e4
Merge branch 'master' into feature/authenticated-microservices
petertrr Oct 27, 2022
39849e7
[skip ci] Fix bean name clash
petertrr Oct 27, 2022
c288726
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 7, 2022
e53392d
[skip ci] WIP: Restore security-related beans in a configuration class
petertrr Nov 9, 2022
2a6fd31
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 9, 2022
7a04db0
[skip ci] Cleanup after merge
petertrr Nov 9, 2022
15ae7e3
[skip ci] Resolve conflict between beans
petertrr Nov 9, 2022
92c4e01
[skip ci] Disable spring-security autoconfiguration on preprocessor; …
petertrr Nov 9, 2022
cd60fe4
[skip ci] Cleanup
petertrr Nov 10, 2022
b20dda2
Code style
petertrr Nov 10, 2022
b9959bc
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 10, 2022
eca3464
Fix compilation
petertrr Nov 10, 2022
45a0c3b
Fix compilation
petertrr Nov 10, 2022
4050086
Merge branch 'master' into feature/authenticated-microservices
petertrr Nov 10, 2022
3c1ff88
`@Component` -> `@Configuration`; remove duplicated imports
petertrr Nov 10, 2022
ec79fd3
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 10, 2022
fd0d57a
Increase logging level; minor refactoring
petertrr Nov 11, 2022
7a25619
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 11, 2022
fee4a56
Fix typo
petertrr Nov 11, 2022
0e2ee9b
Use `securityMatcher`s to glue together multiple security chains
petertrr Nov 11, 2022
c1a97c0
Import `WebClientCustomizers` in backend
petertrr Nov 11, 2022
a2da43b
Bind `orchestrator-sa` ServiceAccount to `microservice` ClusterRole
petertrr Nov 11, 2022
d7c18a8
Improved logging
petertrr Nov 11, 2022
105231c
Different path matcher for k8s-secured endpoints
petertrr Nov 11, 2022
7a390ea
Helm: mount SA token to orchestrator; add missing property in backend…
petertrr Nov 11, 2022
d5581c8
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 11, 2022
454d67b
Merge branch 'master' into feature/authenticated-microservices
petertrr Nov 17, 2022
878822d
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 23, 2022
4844e9b
Move k8s-security-related classes to authService module
petertrr Nov 23, 2022
ff458a2
Code style, minor fixes
petertrr Nov 23, 2022
b42254b
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 23, 2022
b5e581c
Code style
petertrr Nov 23, 2022
9566b3a
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Nov 23, 2022
6a3584c
Try to build w/o caches
petertrr Nov 24, 2022
e3b9d9c
One more experiment to fix Kapt error
petertrr Nov 24, 2022
2afa564
One more experiment to fix Kapt error
petertrr Nov 24, 2022
aa06a2c
One more experiment to fix Kapt error
petertrr Nov 24, 2022
4ae4edd
One more experiment to fix Kapt error
petertrr Nov 24, 2022
ac57556
Remove most of JPA related depdencies from auth service; revert chang…
petertrr Nov 24, 2022
9c3d918
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 8, 2022
804dffa
Follow-ups after merge
petertrr Dec 8, 2022
dce76ae
Update helm_push.yml
petertrr Dec 8, 2022
467483c
Update helm_push.yml
petertrr Dec 8, 2022
f1265ff
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 12, 2022
64bd7df
Fixme until #1247
petertrr Dec 12, 2022
bbd1b34
Eclude security autoconfuguration from preprocessor
petertrr Dec 12, 2022
64ac8f2
Merge remote-tracking branch 'origin/feature/authenticated-microservi…
petertrr Dec 12, 2022
2e24b08
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 13, 2022
bc4fb35
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 14, 2022
5813501
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 14, 2022
df910bb
Add more endpoints to exclusion list
petertrr Dec 15, 2022
11a89f5
Merge remote-tracking branch 'origin/master' into feature/authenticat…
petertrr Dec 15, 2022
d9f1f31
Merge branch 'master' into feature/authenticated-microservices
sanyavertolet Apr 5, 2023
ee6e5a1
[skip ci] updated branch
sanyavertolet Apr 5, 2023
5c3c681
[skip ci] added SA to demo and gateway
sanyavertolet Apr 5, 2023
925d062
[skip ci] wrapper tokenewviews
sanyavertolet Apr 5, 2023
fb80354
Merge branch 'master' into feature/authenticated-microservices
sanyavertolet Apr 6, 2023
76ce335
Merge branch 'master' into feature/authenticated-microservices
nulls Jul 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions authentication-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ kotlin {
dependencies {
implementation(projects.saveCloudCommon)
implementation(libs.spring.boot.starter.security)
implementation("org.springframework:spring-jdbc")
implementation(libs.spring.security.core)
implementation("org.springframework:spring-jdbc")
implementation(libs.spring.security.config)
implementation(libs.spring.security.web)
implementation(libs.spring.boot.autoconfigure) {
because("This dependency contains `ConditionalOnCloudPlatform`")
}
implementation(libs.fabric8.kubernetes.client)
testImplementation(libs.spring.security.test)
testImplementation(libs.junit.jupiter.api)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Customization for spring `WebClient`
*/

package com.saveourtool.save.authservice.config

import com.saveourtool.save.authservice.utils.SA_HEADER_NAME
import com.saveourtool.save.utils.debug
import com.saveourtool.save.utils.getLogger
import org.springframework.beans.factory.annotation.Value

import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform
import org.springframework.boot.cloud.CloudPlatform
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.ClientRequest
import org.springframework.web.reactive.function.client.WebClient

import java.nio.file.Path
import java.time.Duration
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference

import kotlin.io.path.readText

/**
* A configuration class that can be used to import all related [WebClientCustomizer] beans.
*/
@Configuration
open class SecurityWebClientCustomizers {
@Bean

Check failure

Code scanning / ktlint

[KDOC_WITHOUT_RETURN_TAG] all methods which return values should have @return tag in KDoc: serviceAccountTokenHeaderWebClientCustomizer

[KDOC_WITHOUT_RETURN_TAG] all methods which return values should have @return tag in KDoc: serviceAccountTokenHeaderWebClientCustomizer
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_CLASS_ELEMENTS")
open fun serviceAccountTokenHeaderWebClientCustomizer(
@Value("\${com.saveourtool.cloud.kubernetes.sa-token.expiration.minutes:5}") expirationTimeMinutes: Long
) = ServiceAccountTokenHeaderWebClientCustomizer(expirationTimeMinutes)
}

/**
* A [WebClientCustomizer] that appends Kubernetes' ServiceAccount token as a custom header.
*
* @param expirationTimeMinutes for how long token should be reused from memory before reading it from the file again.
*/
class ServiceAccountTokenHeaderWebClientCustomizer(
expirationTimeMinutes: Long,
) : WebClientCustomizer {
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
private val logger = getLogger<ServiceAccountTokenHeaderWebClientCustomizer>()
private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(expirationTimeMinutes)) {
val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText()
token
}

override fun customize(builder: WebClient.Builder) {
builder.filter { request, next ->
val token = wrapper.getValue()
logger.debug { "Appending `$SA_HEADER_NAME` header to the request ${request.method()} to ${request.url()}" }
ClientRequest.from(request)
.header(SA_HEADER_NAME, token)
.build()
.let(next::exchange)
}
}
}

/**
* A wrapper around a value of type [T] that caches it for [expirationTimeMillis] and then recalculates
* using [valueGetter]
*
* @param expirationTime value expiration time
* @property valueGetter a function to calculate the value of type [T]
*/
class ExpiringValueWrapper<T : Any>(
expirationTime: Duration,
private val valueGetter: () -> T,
) {
private val expirationTimeMillis = expirationTime.toMillis()
private val lastUpdateTimeMillis = AtomicLong(0)
private val value: AtomicReference<T> = AtomicReference()

/**
* @return cached value or refreshes the value and returns it
*/
fun getValue(): T {
val current = System.currentTimeMillis()
if (current - lastUpdateTimeMillis.get() > expirationTimeMillis) {
value.lazySet(valueGetter())
lastUpdateTimeMillis.lazySet(current)
}
return value.get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.saveourtool.save.v1
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Profile
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.http.HttpStatus
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
Expand All @@ -23,22 +25,34 @@ import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers

import javax.annotation.PostConstruct

/**
* Common configuration for web security which exposes [SecurityWebFilterChain] beans.
* Note: configuration of [ServerHttpSecurity] should start with [ServerHttpSecurity.securityMatcher] invocation
* to be able to use multiple [SecurityWebFilterChain]s. See [comments form this answer](https://stackoverflow.com/a/54792674)
* for details.
*/
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@Profile("secure")
@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION")
@Suppress("MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION")
class WebSecurityConfig(
private val authenticationManager: ConvertingAuthenticationManager,
@Autowired private var defaultMethodSecurityExpressionHandler: DefaultMethodSecurityExpressionHandler
) {
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
fun securityWebFilterChain(
http: ServerHttpSecurity
): SecurityWebFilterChain = http.run {
authorizeExchange()
securityMatcher(
// This `SecurityWebFilterChain` should be applicable to all requests not matched above
ServerWebExchangeMatchers.anyExchange()
)
.authorizeExchange()
.pathMatchers(*publicEndpoints.toTypedArray())
.permitAll()
// resources for frontend
Expand Down Expand Up @@ -120,7 +134,9 @@ class NoopWebSecurityConfig {
@Bean
fun securityWebFilterChain(
http: ServerHttpSecurity
): SecurityWebFilterChain = http.authorizeExchange()
): SecurityWebFilterChain = http
.securityMatcher(ServerWebExchangeMatchers.anyExchange())
.authorizeExchange()
.anyExchange()
.permitAll()
.and()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.saveourtool.save.authservice.utils.IdentitySourceAwareUserDetails
import com.saveourtool.save.authservice.utils.extractUserNameAndIdentitySource

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Primary
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Expand All @@ -21,6 +22,7 @@ import reactor.kotlin.core.publisher.switchIfEmpty
* where user identity is already guaranteed.
*/
@Component
@Primary
class ConvertingAuthenticationManager(
@Autowired private var authenticationUserDetailsService: AuthenticationUserDetailsService
) : ReactiveAuthenticationManager {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Utilities to configure Kubernetes ServiceAccount token-based authentication in Spring Security.
*/

package com.saveourtool.save.authservice.utils

import com.saveourtool.save.utils.debug
import com.saveourtool.save.utils.getLogger

import io.fabric8.kubernetes.api.model.authentication.TokenReview
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.utils.Serialization
import org.intellij.lang.annotations.Language
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform
import org.springframework.boot.cloud.CloudPlatform
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.core.annotation.Order
import org.springframework.http.HttpStatus
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty
import reactor.kotlin.core.publisher.toMono

const val SA_HEADER_NAME = "X-Service-Account-Token"

/**
* A Configuration class that can be used to import all related beans to set up Spring Security
* to work with Kubernetes ServiceAccount tokens.
*/
@Configuration
@Import(
ServiceAccountTokenExtractorConverter::class,
ServiceAccountAuthenticatingManager::class,
)
@Suppress(
"AVOID_USING_UTILITY_CLASS", // Spring beans need to be declared inside `@Configuration` class.
)
class KubernetesAuthenticationUtils {
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@Bean
@Order(2)
@Suppress(
"MISSING_KDOC_CLASS_ELEMENTS",
"MISSING_KDOC_ON_FUNCTION",
)
fun internalSecuredSecurityChain(
http: ServerHttpSecurity,
serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager,
serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter,
): SecurityWebFilterChain = http.run {
securityMatcher(
NegatedServerWebExchangeMatcher(
ServerWebExchangeMatchers.pathMatchers("/api/**", "/sandbox/api/**")
)
)
.authorizeExchange()
.pathMatchers("/actuator/**")
// all requests to `/actuator` should be sent only from inside the cluster
// access to this port should be controlled by a NetworkPolicy
.permitAll()
.and()
.authorizeExchange()
.pathMatchers("/**")
.authenticated()
.and()
.serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager)
.csrf()
.disable()
.logout()
.disable()
.formLogin()
.disable()
.build()
}

/**
* No-op security config when not running in Kubernetes.
* FixMe: can be removed in favor of common `WebSecurityConfig` from authService?
*/
@ConditionalOnCloudPlatform(CloudPlatform.NONE)
@Bean
@Order(2)
@Suppress(
"MISSING_KDOC_CLASS_ELEMENTS",
"MISSING_KDOC_ON_FUNCTION",
"KDOC_WITHOUT_PARAM_TAG",
"KDOC_WITHOUT_RETURN_TAG",
)
fun internalInsecureSecurityChain(
http: ServerHttpSecurity
): SecurityWebFilterChain = http.run {
securityMatcher(
ServerWebExchangeMatchers.pathMatchers("/internal/**", "/actuator/**")
)
.authorizeExchange()
.pathMatchers("/internal/**", "/actuator/**")
.permitAll()
.and()
.csrf()
.disable()
.build()
}
}

/**
* A [ServerAuthenticationConverter] that attempts to convert a [ServerWebExchange] to an [Authentication]
* if it encounters a SA token in [SA_HEADER_NAME] header.
*/
@Component
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter {
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
private val logger = getLogger<ServiceAccountTokenExtractorConverter>()
override fun convert(exchange: ServerWebExchange): Mono<Authentication> = Mono.justOrEmpty(
exchange.request.headers[SA_HEADER_NAME]?.firstOrNull()
).map { token ->
logger.debug { "Starting to process `$SA_HEADER_NAME` of an incoming request [${exchange.request.method} ${exchange.request.uri}]" }
PreAuthenticatedAuthenticationToken("TokenSupplier", token)
}
}

/**
* A [ReactiveAuthenticationManager] that is intended to be used together with [ServerAuthenticationConverter].
* Attempts to authenticate an [Authentication] validating ServiceAccount token using TokeReview API.
*/
@Component
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
class ServiceAccountAuthenticatingManager(
private val kubernetesClient: KubernetesClient,
) : ReactiveAuthenticationManager {
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
private val logger = getLogger<ServiceAccountAuthenticatingManager>()
override fun authenticate(authentication: Authentication): Mono<Authentication> = authentication.toMono()
.filter { it is PreAuthenticatedAuthenticationToken }
.map { preAuthenticatedAuthenticationToken ->
val tokenReview = tokenReviewSpec(preAuthenticatedAuthenticationToken.credentials as String)
logger.debug {
"Will create k8s resource from the following YAML:\n${tokenReview.prependIndent(" ")}"
}
val response = kubernetesClient.resource(tokenReview).createOrReplace() as TokenReview
logger.debug {
"Got the following response from the API server:\n${
Serialization.yamlMapper().writeValueAsString(response).prependIndent(" ")
}"
}
response
}
.filter { response ->
val isAuthenticated = response.status.error.isNullOrEmpty() && response.status.authenticated
logger.debug { "After the response from TokenReview, request authentication is $isAuthenticated" }
isAuthenticated
}
.map<Authentication> {
with(authentication) {
UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities)
}
}
.switchIfEmpty {
Mono.error { BadCredentialsException("Invalid token") }
}

@Language("YAML")
private fun tokenReviewSpec(token: String): String = """
|apiVersion: authentication.k8s.io/v1
|kind: TokenReview
|metadata:
| name: service-account-validity-check
| namespace: ${kubernetesClient.namespace}
|spec:
| token: $token
""".trimMargin()
}

/**
* Configures authentication and authorization using Kubernetes ServiceAccount tokens.
* This method requires two beans which can be imported with [KubernetesAuthenticationUtils] configuration class.
*/
@Suppress("KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG")
fun ServerHttpSecurity.serviceAccountTokenAuthentication(
serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter,
serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager,
): ServerHttpSecurity = addFilterBefore(
AuthenticationWebFilter(serviceAccountAuthenticatingManager).apply {
setServerAuthenticationConverter(serviceAccountTokenExtractorConverter)
},
SecurityWebFiltersOrder.HTTP_BASIC
)
.exceptionHandling {
it.authenticationEntryPoint(
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)
)
}
Loading