From 8009db2ec9289479806abac3543ca7fda0535c93 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 15 Sep 2022 15:39:36 +0300 Subject: [PATCH 01/57] Improvements for Gradle build * Precompiled script plugin for kotlin-jvm plugin application and configuration * Move some dependencies to libs.versions.toml --- api-gateway/build.gradle.kts | 13 +-------- .../kotlin-jvm-configuration.gradle.kts | 27 +++++++++++++++++++ .../spring-boot-configuration.gradle.kts | 4 +-- gradle/libs.versions.toml | 3 +++ save-api-cli/build.gradle.kts | 13 ++------- save-api/build.gradle.kts | 12 ++------- save-backend/build.gradle.kts | 16 ++--------- save-cloud-common/build.gradle.kts | 2 +- save-orchestrator/build.gradle.kts | 10 +------ save-preprocessor/build.gradle.kts | 13 +-------- test-utils/build.gradle.kts | 16 ++--------- 11 files changed, 44 insertions(+), 85 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/kotlin-jvm-configuration.gradle.kts diff --git a/api-gateway/build.gradle.kts b/api-gateway/build.gradle.kts index f1a7eec3de..ce3d8190ae 100644 --- a/api-gateway/build.gradle.kts +++ b/api-gateway/build.gradle.kts @@ -4,21 +4,10 @@ import com.saveourtool.save.buildutils.configureSpotless import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") id("com.saveourtool.save.buildutils.spring-boot-configuration") } -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } -} - -tasks.withType { - useJUnitPlatform() -} - dependencies { api(projects.saveCloudCommon) implementation(libs.spring.cloud.starter.gateway) diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/kotlin-jvm-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/kotlin-jvm-configuration.gradle.kts new file mode 100644 index 0000000000..c1a1095b1c --- /dev/null +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/kotlin-jvm-configuration.gradle.kts @@ -0,0 +1,27 @@ +package com.saveourtool.save.buildutils + +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.tasks.testing.Test +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.* +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension +import org.springframework.boot.gradle.dsl.SpringBootExtension +import org.springframework.boot.gradle.tasks.bundling.BootBuildImage +import org.springframework.boot.gradle.tasks.run.BootRun +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +tasks.withType { + kotlinOptions { + jvmTarget = Versions.jdk + freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" + } +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts index 6fa1011b71..00fd94f351 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts @@ -33,14 +33,14 @@ dependencies { add("implementation", libs.jackson.module.kotlin) add("implementation", libs.slf4j.api) add("implementation", libs.logback.core) - add("implementation", "io.projectreactor.kotlin:reactor-kotlin-extensions") + add("implementation", libs.reactor.kotlin.extensions) add("implementation", libs.springdoc.openapi.ui) add("runtimeOnly", libs.springdoc.openapi.webflux.ui) add("runtimeOnly", libs.springdoc.openapi.security) add("runtimeOnly", libs.springdoc.openapi.kotlin) add("implementation", libs.swagger.annotations) - add("kapt", "org.springframework.boot:spring-boot-configuration-processor:${libs.versions.spring.boot.get()}") + add("kapt", libs.spring.boot.configuration.processor) add("testImplementation", libs.spring.boot.starter.test) add("testImplementation", libs.mockito.kotlin) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b66321a43..214c95eed5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,6 +85,7 @@ spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-star spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot-starter-data-jpa" } spring-boot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz" } spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" } +spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } spring-security-core = { module = "org.springframework.security:spring-security-core" } spring-security-oauth2-client = { module = "org.springframework.security:spring-security-oauth2-client" } spring-security-test = { module = "org.springframework.security:spring-security-test" } @@ -156,6 +157,8 @@ picocli = { module = "info.picocli:picocli", version.ref = "picocli" } zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } kotlinx-cli = { module = "org.jetbrains.kotlinx:kotlinx-cli", version.ref = "kotlinx-cli" } gradle-plugin-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } +reactor-kotlin-extensions = { module = "io.projectreactor.kotlin:reactor-kotlin-extensions" } +reactor-extra = { module = "io.projectreactor.addons:reactor-extra" } fabric8-kubernetes-client = { module = "io.fabric8:kubernetes-client", version.ref = "fabric8" } fabric8-kubernetes-server-mock = { module = "io.fabric8:kubernetes-server-mock", version.ref = "fabric8" } diff --git a/save-api-cli/build.gradle.kts b/save-api-cli/build.gradle.kts index 47cd087e5e..280d068bed 100644 --- a/save-api-cli/build.gradle.kts +++ b/save-api-cli/build.gradle.kts @@ -1,8 +1,6 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { application - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") alias(libs.plugins.kotlin.plugin.serialization) } @@ -10,16 +8,9 @@ application { mainClass.set("com.saveourtool.save.apicli.MainKt") } -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } -} - kotlin { jvmToolchain { - (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) + this.languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } } diff --git a/save-api/build.gradle.kts b/save-api/build.gradle.kts index a4bf0134be..594ff35fb0 100644 --- a/save-api/build.gradle.kts +++ b/save-api/build.gradle.kts @@ -1,22 +1,14 @@ import com.saveourtool.save.buildutils.configurePublishing -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") alias(libs.plugins.kotlin.plugin.serialization) `maven-publish` } -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } -} - kotlin { jvmToolchain { - (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) + this.languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } } diff --git a/save-backend/build.gradle.kts b/save-backend/build.gradle.kts index eab8e70c65..08c8afbf3c 100644 --- a/save-backend/build.gradle.kts +++ b/save-backend/build.gradle.kts @@ -3,13 +3,12 @@ import de.undercouch.gradle.tasks.download.Download import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.gradle.testing.jacoco.plugins.JacocoTaskExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.nio.file.Files.isDirectory import java.nio.file.Paths plugins { - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") id("com.saveourtool.save.buildutils.spring-boot-configuration") id("com.saveourtool.save.buildutils.spring-data-configuration") // this plugin will generate generateOpenApiDocs task @@ -28,13 +27,6 @@ openApi { } } -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } -} - tasks.named("processTestResources") { dependsOn("copyLiquibase") } @@ -44,10 +36,6 @@ tasks.register("copyLiquibase") { into("$buildDir/resources/test/db") } -tasks.withType { - useJUnitPlatform() -} - tasks.register("cleanupDbAndStorage") { dependsOn(":liquibaseDropAll") val profile = properties["save.profile"] as String? @@ -130,7 +118,7 @@ dependencies { implementation(libs.spring.security.core) implementation(libs.hibernate.micrometer) implementation(libs.spring.cloud.starter.kubernetes.client.config) - implementation("io.projectreactor.addons:reactor-extra") + implementation(libs.reactor.extra) testImplementation(libs.spring.security.test) testImplementation(projects.testUtils) } diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index 2e964ad450..b304f6d50f 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -22,7 +22,7 @@ kotlin { } } jvmToolchain { - (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) + this.languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } js(BOTH).browser() diff --git a/save-orchestrator/build.gradle.kts b/save-orchestrator/build.gradle.kts index 318a1ff7f2..7fb6a75f00 100644 --- a/save-orchestrator/build.gradle.kts +++ b/save-orchestrator/build.gradle.kts @@ -3,7 +3,7 @@ import com.saveourtool.save.buildutils.* import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") id("com.saveourtool.save.buildutils.spring-boot-configuration") id("de.undercouch.download") // can't use `alias`, because this plugin is a transitive dependency of kotlin-gradle-plugin id("org.gradle.test-retry") version "1.4.1" @@ -12,15 +12,7 @@ plugins { configureJacoco() configureSpotless() -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" + "-Xcontext-receivers" - } -} - tasks.withType { - useJUnitPlatform() retry { // There once were flaky tests in orchestrator, but it seems like they became stable. // Settings can be restored or removed, as required. diff --git a/save-preprocessor/build.gradle.kts b/save-preprocessor/build.gradle.kts index de47944e14..afd81dc3e7 100644 --- a/save-preprocessor/build.gradle.kts +++ b/save-preprocessor/build.gradle.kts @@ -2,7 +2,7 @@ import com.saveourtool.save.buildutils.configureJacoco import com.saveourtool.save.buildutils.configureSpotless plugins { - kotlin("jvm") + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") alias(libs.plugins.kotlin.plugin.serialization) id("com.saveourtool.save.buildutils.spring-boot-configuration") } @@ -23,16 +23,5 @@ dependencies { implementation(libs.commons.compress) } -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } -} - -tasks.withType { - useJUnitPlatform() -} - configureJacoco() configureSpotless() diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index efa6b163b3..07a2a683d8 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -1,27 +1,15 @@ import com.saveourtool.save.buildutils.configureSpotless -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") -} - -tasks.withType { - kotlinOptions { - jvmTarget = Versions.jdk - freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" - } + id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") } kotlin { jvmToolchain { - (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) + this.languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } } -tasks.withType { - useJUnitPlatform() -} - dependencies { implementation(libs.okhttp.mockwebserver) implementation(libs.okhttp) From d75fe3caf56b6b8795f8fe6d96e6c8820b3b5829 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 16 Sep 2022 15:39:02 +0300 Subject: [PATCH 02/57] Switch `spring-cloud-kubernetes` implementation from `kubernetes-client` to `fabric8-client` in save-backend --- gradle/libs.versions.toml | 3 +++ save-backend/build.gradle.kts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 214c95eed5..0380f2df57 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,6 +29,7 @@ testcontainers = "1.17.3" okhttp3 = "4.10.0" reckon = "0.16.1" commons-compress = "1.21" +commons-io = "2.11.0" picocli = "4.6.3" zip4j = "2.11.1" ktoml = "0.2.13" @@ -92,6 +93,7 @@ spring-security-test = { module = "org.springframework.security:spring-security- spring-boot-gradle-plugin = { module = "org.springframework.boot:spring-boot-gradle-plugin", version.ref = "spring-boot" } spring-cloud-starter-gateway = { module = "org.springframework.cloud:spring-cloud-starter-gateway", version.ref = "spring-cloud" } spring-cloud-starter-kubernetes-client-config = { module = "org.springframework.cloud:spring-cloud-starter-kubernetes-client-config", version.ref = "spring-cloud-kubernetes" } +spring-cloud-starter-kubernetes-fabric8-config = { module = "org.springframework.cloud:spring-cloud-starter-kubernetes-fabric8-config", version.ref = "spring-cloud-kubernetes" } spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" } spring-context-indexer = { module = "org.springframework:spring-context-indexer", version.ref = "spring" } spring-kafka = { module = "org.springframework.kafka:spring-kafka" } @@ -153,6 +155,7 @@ diktat-gradle-plugin = { module = "org.cqfn.diktat:diktat-gradle-plugin", versio detekt-gradle-plugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } reckon-gradle-plugin = { module = "org.ajoberstar.reckon:reckon-gradle", version.ref = "reckon" } commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commons-compress" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } kotlinx-cli = { module = "org.jetbrains.kotlinx:kotlinx-cli", version.ref = "kotlinx-cli" } diff --git a/save-backend/build.gradle.kts b/save-backend/build.gradle.kts index 08c8afbf3c..3621434273 100644 --- a/save-backend/build.gradle.kts +++ b/save-backend/build.gradle.kts @@ -117,8 +117,9 @@ dependencies { implementation(libs.spring.boot.starter.security) implementation(libs.spring.security.core) implementation(libs.hibernate.micrometer) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) implementation(libs.reactor.extra) + implementation(libs.commons.io) testImplementation(libs.spring.security.test) testImplementation(projects.testUtils) } From 8796ed727d5dc0b74fa09ce38860e63d802501d2 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 16 Sep 2022 17:49:42 +0300 Subject: [PATCH 03/57] [skip ci] [WIP] App-to-app authentication using ServiceAccount tokens --- .../save/backend/configs/WebSecurityConfig.kt | 65 +++++++++++++++--- .../utils/ConvertingAuthenticationManager.kt | 2 + .../CustomAuthenticationBasicConverter.kt | 4 +- .../utils/KubernetesAuthenticationUtils.kt | 67 +++++++++++++++++++ .../templates/backend-deployment.yaml | 9 +++ .../templates/service-accounts.yaml | 56 ++++++++++++++++ 6 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt create mode 100644 save-cloud-charts/save-cloud/templates/service-accounts.yaml diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 2c7322ea4e..cd597a87d5 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -6,11 +6,14 @@ package com.saveourtool.save.backend.configs import com.saveourtool.save.backend.utils.ConvertingAuthenticationManager import com.saveourtool.save.backend.utils.CustomAuthenticationBasicConverter +import com.saveourtool.save.backend.utils.ServiceAccountAuthenticatingManager +import com.saveourtool.save.backend.utils.ServiceAccountTokenExtractorConverter import com.saveourtool.save.domain.Role 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.annotation.Order import org.springframework.http.HttpStatus import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler import org.springframework.security.access.hierarchicalroles.RoleHierarchy @@ -25,6 +28,9 @@ 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.AndServerWebExchangeMatcher +import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers import javax.annotation.PostConstruct @EnableWebFluxSecurity @@ -33,27 +39,35 @@ import javax.annotation.PostConstruct @Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") class WebSecurityConfig( private val authenticationManager: ConvertingAuthenticationManager, + private val serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, + private val serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, ) { @Autowired private lateinit var defaultMethodSecurityExpressionHandler: DefaultMethodSecurityExpressionHandler @Bean + @Order(1) fun securityWebFilterChain( http: ServerHttpSecurity ): SecurityWebFilterChain = http.run { - // All `/internal/**` and `/actuator/**` requests should be sent only from internal network, - // they are not proxied from gateway. - authorizeExchange() - .pathMatchers("/", "/internal/**", "/actuator/**", *publicEndpoints.toTypedArray()) - .permitAll() - // resources for frontend - .pathMatchers("/*.html", "/*.js*", "/*.css", "/img/**", "/*.ico", "/*.png", "/particles.json") - .permitAll() + securityMatcher( + AndServerWebExchangeMatcher( + ServerWebExchangeMatchers.anyExchange(), + NegatedServerWebExchangeMatcher( + ServerWebExchangeMatchers.pathMatchers("/actuator", "/actuator/**", "/internal/**") + ) + ) + ) } + .run { + authorizeExchange() + .pathMatchers(*publicEndpoints.toTypedArray()) + .permitAll() + } .and() .run { authorizeExchange() - .pathMatchers("/**") + .pathMatchers("/api/**") .authenticated() } .and() @@ -120,10 +134,43 @@ class WebSecurityConfig( "/api/$v1/contests/*/*/best", ) } + + @Profile("kubernetes") + @Bean + @Order(2) + fun internalSecuredSecurityChain( + http: ServerHttpSecurity, + ): SecurityWebFilterChain = http.run { + authorizeExchange().pathMatchers("/internal/**", "/actuator/**") + .authenticated() + .and() + .addFilterBefore( + AuthenticationWebFilter(serviceAccountAuthenticatingManager).apply { + setServerAuthenticationConverter(serviceAccountTokenExtractorConverter) + }, + SecurityWebFiltersOrder.HTTP_BASIC + ) + .build() + } + + @Profile("!kubernetes") + @Bean + @Order(2) + fun internalInsecureSecurityChain( + http: ServerHttpSecurity + ): SecurityWebFilterChain = http.run { + // All `/internal/**` and `/actuator/**` requests should be sent only from internal network, + // they are not proxied from gateway. + authorizeExchange().pathMatchers("/internal/**", "/actuator/**") + .permitAll() + .and() + .build() + } } @EnableWebFluxSecurity @Profile("!secure") +@Order(1) @Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") class NoopWebSecurityConfig { @Bean diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt index 07601f63b4..056e765b5c 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt @@ -3,6 +3,7 @@ package com.saveourtool.save.backend.utils import com.saveourtool.save.backend.service.UserDetailsService import com.saveourtool.save.utils.IdentitySourceAwareUserDetails 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 @@ -17,6 +18,7 @@ import reactor.kotlin.core.publisher.switchIfEmpty * where user identity is already guaranteed. */ @Component +@Primary class ConvertingAuthenticationManager : ReactiveAuthenticationManager { @Autowired private lateinit var userDetailsService: UserDetailsService diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/CustomAuthenticationBasicConverter.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/CustomAuthenticationBasicConverter.kt index a7734bea1c..bd58149047 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/CustomAuthenticationBasicConverter.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/CustomAuthenticationBasicConverter.kt @@ -3,6 +3,7 @@ package com.saveourtool.save.backend.utils import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.Authentication import org.springframework.security.web.server.authentication.ServerAuthenticationConverter +import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono @@ -11,8 +12,7 @@ import reactor.core.publisher.Mono * Implementation of [ServerAuthenticationConverter] that embeds user identity source into [UsernamePasswordAuthenticationToken] */ @Component -class CustomAuthenticationBasicConverter : org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter(), -ServerAuthenticationConverter { +class CustomAuthenticationBasicConverter : ServerHttpBasicAuthenticationConverter(), ServerAuthenticationConverter { /** * Convert exchange, received from gateway into UsernamePasswordAuthenticationToken, specify source identity, laid * by gateway into X-Authorization-Source header diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt new file mode 100644 index 0000000000..d345addd03 --- /dev/null +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt @@ -0,0 +1,67 @@ +package com.saveourtool.save.backend.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.security.authentication.ReactiveAuthenticationManager +import org.springframework.security.core.Authentication +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken +import org.springframework.security.web.server.authentication.ServerAuthenticationConverter +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono + +@Component +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { + override fun convert(exchange: ServerWebExchange): Mono { + return Mono.justOrEmpty( + exchange.request.headers["X-Service-Account-Token"]?.firstOrNull() + ).map { token -> + PreAuthenticatedAuthenticationToken("TokenSupplier", token) + } + } +} + +@Component +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +class ServiceAccountAuthenticatingManager( +// val kubernetesClient: ApiClient, + val kubernetesClient: KubernetesClient, +) : ReactiveAuthenticationManager { + override fun authenticate(authentication: Authentication): Mono { + val token = (authentication as PreAuthenticatedAuthenticationToken).credentials + @Language("yaml") + val tokenReview = """ + |apiVersion: authentication.k8s.io/v1 + |kind: TokenReview + |metadata: + | name: service-account-validity-check + | namespace: ${kubernetesClient.namespace} + |spec: + | token: $token + """.trimMargin() +/* val tokenReview = V1TokenReview().apply { + spec = V1TokenReviewSpec().apply { + setToken(token) + } + } + AuthenticationV1Api(kubernetesClient).createTokenReview(tokenReview)*/ + 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(" ")}" + } + authentication.isAuthenticated = response.status.error == null && response.status.authenticated + return Mono.just(authentication) + } + + private val logger = getLogger() +} diff --git a/save-cloud-charts/save-cloud/templates/backend-deployment.yaml b/save-cloud-charts/save-cloud/templates/backend-deployment.yaml index 571489358b..eef59ccafc 100644 --- a/save-cloud-charts/save-cloud/templates/backend-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-deployment.yaml @@ -18,6 +18,7 @@ spec: annotations: {{- include "pod.common.annotations" (dict "service" .Values.backend ) | nindent 8 }} spec: + serviceAccountName: microservice-sa restartPolicy: Always {{- include "cnb.securityContext" . | nindent 6 }} containers: @@ -35,6 +36,8 @@ spec: mountPath: /home/cnb/files - name: database-secret mountPath: {{ .Values.backend.dbPasswordFile }} + - name: service-account-projected-token + mountPath: /var/run/secrets/tokens {{- include "spring-boot.management" .Values.backend | nindent 10 }} resources: limits: @@ -109,3 +112,9 @@ spec: secretName: db-secrets - name: migrations-data emptyDir: {} + - name: service-account-projected-token + projected: + sources: + - serviceAccountToken: + path: sa-token + expirationSeconds: 7200 diff --git a/save-cloud-charts/save-cloud/templates/service-accounts.yaml b/save-cloud-charts/save-cloud/templates/service-accounts.yaml new file mode 100644 index 0000000000..67eb9f3730 --- /dev/null +++ b/save-cloud-charts/save-cloud/templates/service-accounts.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: microservice-sa + +--- + +# https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#service-account +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: microservice +rules: + - apiGroups: [""] # "" indicates the core API group + resources: [configmaps, secrets] + verbs: [list, get, watch] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: microservice-role-binding +subjects: + - kind: ServiceAccount + name: microservice-sa +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: microservice + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: microservice +rules: + - apiGroups: ["authentication.k8s.io"] + resources: [tokenreviews] + verbs: ["create"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: microservice-role-binding +subjects: + - kind: ServiceAccount + name: microservice-sa + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: microservice \ No newline at end of file From 90c27ca59ca2c48a262e889376d0ef533559efc5 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 29 Sep 2022 15:54:41 +0300 Subject: [PATCH 04/57] [skip ci] WIP: authenticated microservices * Added WebFilter for applying token in headers --- save-backend/build.gradle.kts | 2 +- .../ServiceTokenHeaderResponseFilter.kt | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt diff --git a/save-backend/build.gradle.kts b/save-backend/build.gradle.kts index e1441e6cd4..749c9b2171 100644 --- a/save-backend/build.gradle.kts +++ b/save-backend/build.gradle.kts @@ -117,7 +117,7 @@ dependencies { implementation(libs.spring.boot.starter.security) implementation(libs.spring.security.core) implementation(libs.hibernate.micrometer) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) implementation(libs.reactor.extra) implementation(libs.commons.io) testImplementation(libs.spring.security.test) diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt new file mode 100644 index 0000000000..06fe13c274 --- /dev/null +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt @@ -0,0 +1,22 @@ +package com.saveourtool.save.preprocessor.filters + +import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform +import org.springframework.boot.cloud.CloudPlatform +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono +import java.nio.file.Path +import kotlin.io.path.readText + +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +@Component +class ServiceTokenHeaderResponseFilter : WebFilter { + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() + exchange.response.headers + .add("X-Service-Account-Token", token) + return chain.filter(exchange) + } +} From 12f01d93ad58bd33a1a05c905297575887fabd93 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 5 Oct 2022 08:42:25 +0300 Subject: [PATCH 05/57] [skip ci] Extract sa-token mount into _helpers.tpl; fix path to the mounted token --- .../save-cloud/templates/_helpers.tpl | 14 ++++++++++++++ .../save-cloud/templates/backend-deployment.yaml | 10 ++-------- .../templates/preprocessor-deployment.yaml | 2 ++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/save-cloud-charts/save-cloud/templates/_helpers.tpl b/save-cloud-charts/save-cloud/templates/_helpers.tpl index b14136597b..ffe1fe6d6a 100644 --- a/save-cloud-charts/save-cloud/templates/_helpers.tpl +++ b/save-cloud-charts/save-cloud/templates/_helpers.tpl @@ -79,4 +79,18 @@ configMap: items: - key: application.properties path: application.properties +{{- end}} + +{{- define "spring-boot.sa-token-mount" -}} +name: service-account-projected-token +mountPath: /var/run/secrets/tokens +{{- end }} + +{{- define "spring-boot.sa-token-volume" -}} +name: service-account-projected-token +projected: + sources: + - serviceAccountToken: + path: service-account-projected-token + expirationSeconds: 7200 {{- end}} \ No newline at end of file diff --git a/save-cloud-charts/save-cloud/templates/backend-deployment.yaml b/save-cloud-charts/save-cloud/templates/backend-deployment.yaml index 2482e823ab..383e451e3e 100644 --- a/save-cloud-charts/save-cloud/templates/backend-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-deployment.yaml @@ -36,8 +36,7 @@ spec: mountPath: /home/cnb/files - name: database-secret mountPath: {{ .Values.mysql.dbPasswordFile }} - - name: service-account-projected-token - mountPath: /var/run/secrets/tokens + - {{ include "spring-boot.sa-token-mount" . | indent 14 | trim }} {{- include "spring-boot.management" .Values.backend | nindent 10 }} resources: limits: @@ -112,9 +111,4 @@ spec: secretName: db-secrets - name: migrations-data emptyDir: {} - - name: service-account-projected-token - projected: - sources: - - serviceAccountToken: - path: sa-token - expirationSeconds: 7200 + - {{ include "spring-boot.sa-token-volume" (dict "service" .Values.backend) | indent 10 | trim }} diff --git a/save-cloud-charts/save-cloud/templates/preprocessor-deployment.yaml b/save-cloud-charts/save-cloud/templates/preprocessor-deployment.yaml index c0cbcbc784..cbfc54efe9 100644 --- a/save-cloud-charts/save-cloud/templates/preprocessor-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/preprocessor-deployment.yaml @@ -31,6 +31,7 @@ spec: - {{ include "spring-boot.config-volume-mount" . | indent 14 | trim }} - name: repos-storage mountPath: /home/cnb + - {{ include "spring-boot.sa-token-mount" . | indent 14 | trim }} {{- include "spring-boot.management" .Values.preprocessor | nindent 10 }} resources: limits: @@ -44,3 +45,4 @@ spec: # and each pod of preprocessor can `git clone` on its own. hostPath: path: /tmp/save/repos + - {{ include "spring-boot.sa-token-volume" (dict "service" .Values.backend) | indent 10 | trim }} From bb7f1deae944695ffa1a5822cb18191dfede4271 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 7 Oct 2022 17:22:36 +0300 Subject: [PATCH 06/57] Read ConfigMap using Kubernetes fabric8 client --- save-backend/src/main/resources/bootstrap.yml | 3 ++- save-cloud-charts/save-cloud/templates/backend-configmap.yaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/save-backend/src/main/resources/bootstrap.yml b/save-backend/src/main/resources/bootstrap.yml index 2dba0aed86..f6f50c5865 100644 --- a/save-backend/src/main/resources/bootstrap.yml +++ b/save-backend/src/main/resources/bootstrap.yml @@ -18,7 +18,8 @@ spring: kubernetes: enabled: true config: - enabled: false + enabled: true + paths: /home/cnb/config/application.properties secrets: enabled: true paths: ${DATABASE_SECRETS_PATH} diff --git a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml index 5e6470a62e..cc6726e07c 100644 --- a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml @@ -8,3 +8,5 @@ data: backend.orchestrator-url=http://orchestrator server.shutdown=graceful management.endpoints.web.exposure.include=* + spring.datasource.url=${spring.datasource.backend-url} + logging.level.org.springframework.cloud=DEBUG From 2b0900dda44cec7d8fdb80b654da4177e008aad6 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Tue, 11 Oct 2022 16:21:39 +0300 Subject: [PATCH 07/57] [skip ci] Extract the WebClientCustomizer into save-cloud-common, plug it in in the preprocessor --- gradle/libs.versions.toml | 1 + .../save/backend/configs/WebSecurityConfig.kt | 6 ++++- save-cloud-common/build.gradle.kts | 1 + .../save/configs/WebClientCustomizers.kt | 23 +++++++++++++++++++ .../config/LocalDateTimeConfig.kt | 3 +++ .../TestsPreprocessorToBackendBridge.kt | 4 +++- 6 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 44decf25a4..8448393442 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -101,6 +101,7 @@ spring-data-jpa = { module = "org.springframework.data:spring-data-jpa" } spring-kafka = { module = "org.springframework.kafka:spring-kafka" } spring-kafka-test = { module = "org.springframework.kafka:spring-kafka-test" } spring-web = { module = "org.springframework:spring-web", version.ref = "spring" } +spring-webflux = { module = "org.springframework:spring-webflux", version.ref = "spring" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin" } kafka-clients = { module = "org.apache.kafka:kafka-clients", version.ref = "kafka-client" } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index cd597a87d5..a2e2e0cd44 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -141,7 +141,11 @@ class WebSecurityConfig( fun internalSecuredSecurityChain( http: ServerHttpSecurity, ): SecurityWebFilterChain = http.run { - authorizeExchange().pathMatchers("/internal/**", "/actuator/**") + authorizeExchange().pathMatchers("/actuator/**") + .permitAll() + .and() + .authorizeExchange() + .pathMatchers("/internal/**") .authenticated() .and() .addFilterBefore( diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index 3abd68d379..d9445b8086 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -45,6 +45,7 @@ kotlin { implementation(project.dependencies.platform(libs.spring.boot.dependencies)) implementation(libs.spring.security.core) implementation(libs.spring.web) + implementation(libs.spring.webflux) implementation(libs.spring.boot) implementation(libs.spring.data.jpa) implementation(libs.jackson.module.kotlin) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt new file mode 100644 index 0000000000..c1b0433b66 --- /dev/null +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -0,0 +1,23 @@ +package com.saveourtool.save.configs + +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer +import org.springframework.web.reactive.function.client.ClientRequest +import org.springframework.context.annotation.Bean +import org.springframework.stereotype.Component +import java.nio.file.Path +import kotlin.io.path.readText + +@Component +class WebClientCustomizers { + @Bean +// @ConditionalOnCloudPlatform + fun serviceAccountTokenHeaderWebClientCustomizer() = WebClientCustomizer { builder -> + builder.filter { request, next -> + val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() + ClientRequest.from(request) + .header("X-Service-Account-Token", token) + .build() + .let(next::exchange) + } + } +} \ No newline at end of file diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt index 5c1855c3e6..e087a807c1 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt @@ -14,6 +14,7 @@ package com.saveourtool.save.preprocessor.config import com.saveourtool.save.utils.LocalDateTimeSerializer import com.fasterxml.jackson.databind.SerializationFeature +import com.saveourtool.save.configs.WebClientCustomizers import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.context.annotation.Bean @@ -28,6 +29,7 @@ import java.time.LocalDateTime import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule +import org.springframework.context.annotation.Import internal val json = Json { serializersModule = SerializersModule { @@ -36,6 +38,7 @@ internal val json = Json { } @Configuration +@Import(WebClientCustomizers::class) class LocalDateTimeConfig { @Bean fun jackson2ObjectMapperBuilderCustomizer() = Jackson2ObjectMapperBuilderCustomizer { jacksonObjectMapperBuilder: Jackson2ObjectMapperBuilder -> diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt index b305eb2e09..1c746a72d0 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt @@ -19,16 +19,18 @@ import reactor.core.publisher.Mono import java.time.Instant /** - * A bridge from preprocesor to backend (rest api wrapper) + * A bridge from preprocessor to backend (rest api wrapper) */ @Service class TestsPreprocessorToBackendBridge( configProperties: ConfigProperties, kotlinSerializationWebClientCustomizer: WebClientCustomizer, + serviceAccountTokenHeaderWebClientCustomizer: WebClientCustomizer, ) { private val webClientBackend = WebClient.builder() .baseUrl(configProperties.backend) .apply(kotlinSerializationWebClientCustomizer::customize) + .apply(serviceAccountTokenHeaderWebClientCustomizer::customize) .build() /** From 415c4f6ab87b0c03ec898a5f01ed349160d83d53 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Tue, 11 Oct 2022 17:49:49 +0300 Subject: [PATCH 08/57] [skip ci] Cleanup --- .../save/backend/configs/WebSecurityConfig.kt | 6 ++-- .../utils/KubernetesAuthenticationUtils.kt | 34 +++++++++---------- .../ServiceTokenHeaderResponseFilter.kt | 22 ------------ 3 files changed, 21 insertions(+), 41 deletions(-) delete mode 100644 save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index a2e2e0cd44..ba613fc6cc 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -39,8 +39,6 @@ import javax.annotation.PostConstruct @Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") class WebSecurityConfig( private val authenticationManager: ConvertingAuthenticationManager, - private val serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, - private val serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, ) { @Autowired private lateinit var defaultMethodSecurityExpressionHandler: DefaultMethodSecurityExpressionHandler @@ -140,8 +138,12 @@ class WebSecurityConfig( @Order(2) fun internalSecuredSecurityChain( http: ServerHttpSecurity, + serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, + serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, ): SecurityWebFilterChain = http.run { 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() diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt index d345addd03..89dce52361 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt @@ -15,6 +15,7 @@ import org.springframework.security.web.server.authentication.ServerAuthenticati import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.toMono @Component @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @@ -31,13 +32,12 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { @Component @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) class ServiceAccountAuthenticatingManager( -// val kubernetesClient: ApiClient, val kubernetesClient: KubernetesClient, ) : ReactiveAuthenticationManager { override fun authenticate(authentication: Authentication): Mono { - val token = (authentication as PreAuthenticatedAuthenticationToken).credentials - @Language("yaml") - val tokenReview = """ + return (authentication as PreAuthenticatedAuthenticationToken).credentials.toMono().map { token -> + @Language("yaml") + val tokenReview = """ |apiVersion: authentication.k8s.io/v1 |kind: TokenReview |metadata: @@ -46,21 +46,21 @@ class ServiceAccountAuthenticatingManager( |spec: | token: $token """.trimMargin() -/* val tokenReview = V1TokenReview().apply { - spec = V1TokenReviewSpec().apply { - setToken(token) + 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 } - AuthenticationV1Api(kubernetesClient).createTokenReview(tokenReview)*/ - 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(" ")}" - } - authentication.isAuthenticated = response.status.error == null && response.status.authenticated - return Mono.just(authentication) + .map { response -> + authentication.isAuthenticated = response.status.error == null && response.status.authenticated + authentication + } } private val logger = getLogger() diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt deleted file mode 100644 index 06fe13c274..0000000000 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/filters/ServiceTokenHeaderResponseFilter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.saveourtool.save.preprocessor.filters - -import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform -import org.springframework.boot.cloud.CloudPlatform -import org.springframework.stereotype.Component -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.WebFilter -import org.springframework.web.server.WebFilterChain -import reactor.core.publisher.Mono -import java.nio.file.Path -import kotlin.io.path.readText - -@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -@Component -class ServiceTokenHeaderResponseFilter : WebFilter { - override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { - val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() - exchange.response.headers - .add("X-Service-Account-Token", token) - return chain.filter(exchange) - } -} From 09df2178fc8c2f3f308413f65202077559a80214 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 14 Oct 2022 15:34:36 +0300 Subject: [PATCH 09/57] [Helm] Set another port as a management port for all JVM services --- save-cloud-charts/save-cloud/templates/backend-configmap.yaml | 1 + .../save-cloud/templates/orchestrator-configmap.yaml | 1 + .../save-cloud/templates/preprocessor-configmap.yaml | 1 + save-cloud-charts/save-cloud/templates/sandbox-configmap.yaml | 1 + save-cloud-charts/save-cloud/values.yaml | 4 ++++ 5 files changed, 8 insertions(+) diff --git a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml index cc6726e07c..e6454ae55d 100644 --- a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml @@ -8,5 +8,6 @@ data: backend.orchestrator-url=http://orchestrator server.shutdown=graceful management.endpoints.web.exposure.include=* + management.server.port={{ .Values.backend.managementPort }} spring.datasource.url=${spring.datasource.backend-url} logging.level.org.springframework.cloud=DEBUG diff --git a/save-cloud-charts/save-cloud/templates/orchestrator-configmap.yaml b/save-cloud-charts/save-cloud/templates/orchestrator-configmap.yaml index 1581968919..4acd00fdfd 100644 --- a/save-cloud-charts/save-cloud/templates/orchestrator-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/orchestrator-configmap.yaml @@ -12,6 +12,7 @@ data: server.shutdown=graceful management.endpoints.web.exposure.include=* + management.server.port={{ .Values.orchestrator.managementPort }} orchestrator.agent-settings.orchestrator-url=http://{{ .Values.orchestrator.name }} orchestrator.agent-settings.backend-url=http://{{ .Values.backend.name }} orchestrator.agent-settings.debug=true diff --git a/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml b/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml index f6b9065fda..a192b05872 100644 --- a/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml @@ -8,3 +8,4 @@ data: save.orchestrator=http://orchestrator server.shutdown=graceful management.endpoints.web.exposure.include=* + management.server.port={{ .Values.preprocessor.managementPort }} diff --git a/save-cloud-charts/save-cloud/templates/sandbox-configmap.yaml b/save-cloud-charts/save-cloud/templates/sandbox-configmap.yaml index 80c8f71eab..c4a766fbf7 100644 --- a/save-cloud-charts/save-cloud/templates/sandbox-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/sandbox-configmap.yaml @@ -12,6 +12,7 @@ data: server.shutdown=graceful management.endpoints.web.exposure.include=* + management.server.port={{ .Values.sandbox.managementPort }} sandbox.agent-settings.orchestrator-url=http://{{ .Values.sandbox.name }} sandbox.agent-settings.backend-url=http://{{ .Values.sandbox.name }} sandbox.agent-settings.debug=true diff --git a/save-cloud-charts/save-cloud/values.yaml b/save-cloud-charts/save-cloud/values.yaml index 6a515917de..62f12918d4 100644 --- a/save-cloud-charts/save-cloud/values.yaml +++ b/save-cloud-charts/save-cloud/values.yaml @@ -10,6 +10,7 @@ backend: profile: dev,secure,kubernetes imageName: save-backend containerPort: 5800 + managementPort: 5801 # Fixed ClusterIP can be assigned to make it easier to query backend from services outside Kubernetes. # Should be chosen depending on cluster's network configuration: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address. clusterIP: null @@ -21,6 +22,7 @@ orchestrator: name: orchestrator imageName: save-orchestrator containerPort: 5100 + managementPort: 5101 # Fixed ClusterIP can be assigned to make it easier to query orchestrator from services outside Kubernetes clusterIP: null dockerHost: tcp://${HOST_IP}:2375 @@ -28,6 +30,7 @@ sandbox: name: sandbox imageName: save-sandbox containerPort: 5400 + managementPort: 5401 # Fixed ClusterIP can be assigned to make it easier to query orchestrator from services outside Kubernetes clusterIP: null dockerHost: tcp://${HOST_IP}:2375 @@ -35,6 +38,7 @@ preprocessor: name: preprocessor imageName: save-preprocessor containerPort: 5200 + managementPort: 5201 # Fixed ClusterIP can be assigned to make it easier to query preprocessor from services outside Kubernetes clusterIP: null gateway: From c235283a8051b23966f6fae261941b45a276c8db Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 14 Oct 2022 17:58:00 +0300 Subject: [PATCH 10/57] [skip ci] WIP --- .../utils/KubernetesAuthenticationUtils.kt | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt index 89dce52361..593e12ef7b 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt @@ -8,13 +8,16 @@ 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.security.authentication.BadCredentialsException import org.springframework.security.authentication.ReactiveAuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken import org.springframework.security.web.server.authentication.ServerAuthenticationConverter 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 @Component @@ -24,9 +27,12 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { return Mono.justOrEmpty( exchange.request.headers["X-Service-Account-Token"]?.firstOrNull() ).map { token -> + logger.debug { "Starting to process `X-Service-Account-Token` of an incoming request" } PreAuthenticatedAuthenticationToken("TokenSupplier", token) } } + + private val logger = getLogger() } @Component @@ -35,31 +41,43 @@ class ServiceAccountAuthenticatingManager( val kubernetesClient: KubernetesClient, ) : ReactiveAuthenticationManager { override fun authenticate(authentication: Authentication): Mono { - return (authentication as PreAuthenticatedAuthenticationToken).credentials.toMono().map { token -> - @Language("yaml") - val tokenReview = """ - |apiVersion: authentication.k8s.io/v1 - |kind: TokenReview - |metadata: - | name: service-account-validity-check - | namespace: ${kubernetesClient.namespace} - |spec: - | token: $token - """.trimMargin() - logger.debug { - "Will create k8s resource from the following YAML:\n${tokenReview.prependIndent(" ")}" + return authentication.toMono() + .filter { it is PreAuthenticatedAuthenticationToken } + .map { + val token = it.credentials + @Language("yaml") + val tokenReview = """ + |apiVersion: authentication.k8s.io/v1 + |kind: TokenReview + |metadata: + | name: service-account-validity-check + | namespace: ${kubernetesClient.namespace} + |spec: + | token: $token + """.trimMargin() + 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 } - val response = kubernetesClient.resource(tokenReview).createOrReplace() as TokenReview - logger.debug { - "Got the following response from the API server:\n${ - Serialization.yamlMapper().writeValueAsString(response).prependIndent(" ") - }" + .filter { response -> + val isAuthenticated = response.status.error.isNullOrEmpty() && response.status.authenticated + logger.debug { "After the response from TokenReview, request authentication is $isAuthenticated" } + isAuthenticated } - response - } - .map { response -> - authentication.isAuthenticated = response.status.error == null && response.status.authenticated - authentication + .map { + with (authentication) { + UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities) + } + } + .switchIfEmpty { + Mono.error { BadCredentialsException("Invalid token") } } } From 5c12d11ef4265e31a3c23664fd2f283a86af8337 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 07:23:56 +0300 Subject: [PATCH 11/57] Enable `@ConditionalOnCloudPlatform` for web client customizer bean --- gradle/libs.versions.toml | 1 + save-cloud-common/build.gradle.kts | 1 + .../com/saveourtool/save/configs/WebClientCustomizers.kt | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6987adeb5f..177a3b181d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -87,6 +87,7 @@ spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot- spring-boot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz" } spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" } spring-boot = { module = "org.springframework.boot:spring-boot" } +spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure" } spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } spring-security-core = { module = "org.springframework.security:spring-security-core" } spring-security-oauth2-client = { module = "org.springframework.security:spring-security-oauth2-client" } diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index d9445b8086..e7fbba366b 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -47,6 +47,7 @@ kotlin { implementation(libs.spring.web) implementation(libs.spring.webflux) implementation(libs.spring.boot) + implementation(libs.spring.boot.autoconfigure) implementation(libs.spring.data.jpa) implementation(libs.jackson.module.kotlin) implementation(libs.hibernate.jpa21.api) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index c1b0433b66..95265f9bcd 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -1,5 +1,7 @@ package com.saveourtool.save.configs +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.web.reactive.function.client.ClientRequest import org.springframework.context.annotation.Bean @@ -10,7 +12,7 @@ import kotlin.io.path.readText @Component class WebClientCustomizers { @Bean -// @ConditionalOnCloudPlatform + @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) fun serviceAccountTokenHeaderWebClientCustomizer() = WebClientCustomizer { builder -> builder.filter { request, next -> val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() From 49a25a366215a0f81d66fb1fdf03b1e6b0dd0d99 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 07:44:47 +0300 Subject: [PATCH 12/57] Handle error status code when uploading test suite source snapshot from preprocessor --- .../service/TestsPreprocessorToBackendBridge.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt index 1c746a72d0..e841f96766 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service import org.springframework.web.reactive.function.BodyInserters import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.bodyToMono +import org.springframework.web.server.ResponseStatusException import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.time.Instant @@ -52,6 +53,12 @@ class TestsPreprocessorToBackendBridge( .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData("content", resourceWithContent)) .retrieve() + .onStatus({ !it.is2xxSuccessful }) { + Mono.error( + IllegalStateException("Failed to upload test suite source snapshot", + ResponseStatusException(it.statusCode())) + ) + } .bodyToMono() /** From 1de5b2f57284a83bd74f2295cfd86f6197e95576 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 09:39:41 +0300 Subject: [PATCH 13/57] Disable CSRF so that everything works --- .../save/backend/configs/WebSecurityConfig.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index e47f221af3..2e3b1c30ec 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -154,6 +154,17 @@ class WebSecurityConfig( }, SecurityWebFiltersOrder.HTTP_BASIC ) + .exceptionHandling { + it.authenticationEntryPoint( + HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) + ) + } + .csrf() + .disable() + .logout() + .disable() + .formLogin() + .disable() .build() } From e39051620f95471a5a6f56fbc922ca222d49d263 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 09:59:31 +0300 Subject: [PATCH 14/57] [skip ci] Move KubernetesAuthenticationUtils to save-cloud-common --- gradle/libs.versions.toml | 1 + .../save/backend/configs/WebSecurityConfig.kt | 7 +++++-- save-cloud-common/build.gradle.kts | 2 ++ .../spring/security}/KubernetesAuthenticationUtils.kt | 10 ++++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) rename {save-backend/src/main/kotlin/com/saveourtool/save/backend/utils => save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security}/KubernetesAuthenticationUtils.kt (91%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fcf8d0a36d..157a54d58c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,6 +90,7 @@ spring-boot = { module = "org.springframework.boot:spring-boot" } spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure" } spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } spring-security-core = { module = "org.springframework.security:spring-security-core" } +spring-security-web = { module = "org.springframework.security:spring-security-web" } spring-security-oauth2-client = { module = "org.springframework.security:spring-security-oauth2-client" } spring-security-test = { module = "org.springframework.security:spring-security-test" } spring-boot-gradle-plugin = { module = "org.springframework.boot:spring-boot-gradle-plugin", version.ref = "spring-boot" } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 2e3b1c30ec..2c533ef5ce 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -6,12 +6,14 @@ package com.saveourtool.save.backend.configs import com.saveourtool.save.backend.utils.ConvertingAuthenticationManager import com.saveourtool.save.backend.utils.CustomAuthenticationBasicConverter -import com.saveourtool.save.backend.utils.ServiceAccountAuthenticatingManager -import com.saveourtool.save.backend.utils.ServiceAccountTokenExtractorConverter import com.saveourtool.save.domain.Role +import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils +import com.saveourtool.save.spring.security.ServiceAccountAuthenticatingManager +import com.saveourtool.save.spring.security.ServiceAccountTokenExtractorConverter import com.saveourtool.save.v1 import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import import org.springframework.context.annotation.Profile import org.springframework.core.annotation.Order import org.springframework.http.HttpStatus @@ -36,6 +38,7 @@ import javax.annotation.PostConstruct @EnableWebFluxSecurity @EnableReactiveMethodSecurity @Profile("secure") +@Import(KubernetesAuthenticationUtils::class) @Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") class WebSecurityConfig( private val authenticationManager: ConvertingAuthenticationManager, diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index e7fbba366b..d38b977b59 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -44,6 +44,7 @@ kotlin { dependencies { implementation(project.dependencies.platform(libs.spring.boot.dependencies)) implementation(libs.spring.security.core) + implementation(libs.spring.security.web) implementation(libs.spring.web) implementation(libs.spring.webflux) implementation(libs.spring.boot) @@ -56,6 +57,7 @@ kotlin { implementation(libs.commons.compress) implementation(libs.validation.api) implementation(libs.swagger.annotations) + implementation(libs.fabric8.kubernetes.client) } } val jvmTest by getting { diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt similarity index 91% rename from save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt rename to save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 593e12ef7b..ec6bc4e393 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -1,4 +1,4 @@ -package com.saveourtool.save.backend.utils +package com.saveourtool.save.spring.security import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.getLogger @@ -8,6 +8,8 @@ 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.Configuration +import org.springframework.context.annotation.Import import org.springframework.security.authentication.BadCredentialsException import org.springframework.security.authentication.ReactiveAuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken @@ -20,6 +22,10 @@ import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.switchIfEmpty import reactor.kotlin.core.publisher.toMono +@Configuration +@Import(ServiceAccountTokenExtractorConverter::class, ServiceAccountAuthenticatingManager::class) +open class KubernetesAuthenticationUtils + @Component @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { @@ -38,7 +44,7 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { @Component @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) class ServiceAccountAuthenticatingManager( - val kubernetesClient: KubernetesClient, + private val kubernetesClient: KubernetesClient, ) : ReactiveAuthenticationManager { override fun authenticate(authentication: Authentication): Mono { return authentication.toMono() From 148e0538e325c78fc9ce08c0710f469be26b6b12 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 13:36:55 +0300 Subject: [PATCH 15/57] [skip ci] From f9c098409733842ea119bf8302c578e00baeca87 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 13:59:00 +0300 Subject: [PATCH 16/57] Minor refactoring of WebClientCustomizers --- .../save/configs/WebClientCustomizers.kt | 44 +++++++++++++++++-- .../save/preprocessor/SavePreprocessor.kt | 3 ++ .../config/LocalDateTimeConfig.kt | 3 -- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index 95265f9bcd..13364e3f7d 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -1,25 +1,63 @@ package com.saveourtool.save.configs +import com.saveourtool.save.utils.debug +import com.saveourtool.save.utils.getLogger 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.web.reactive.function.client.ClientRequest import org.springframework.context.annotation.Bean import org.springframework.stereotype.Component +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 @Component class WebClientCustomizers { @Bean @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) - fun serviceAccountTokenHeaderWebClientCustomizer() = WebClientCustomizer { builder -> + fun serviceAccountTokenHeaderWebClientCustomizer() = ServiceAccountTokenHeaderWebClientCustomizer() + + private val logger = getLogger() +} + +class ServiceAccountTokenHeaderWebClientCustomizer : WebClientCustomizer { + private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(5)) { + 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 = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() + val token = wrapper.getValue() + logger.debug { "Appending `X-Service-Account-Token` header to the request ${request.method()} to ${request.url()}" } ClientRequest.from(request) .header("X-Service-Account-Token", token) .build() .let(next::exchange) } } -} \ No newline at end of file + + private val logger = getLogger() +} + +class ExpiringValueWrapper( + expirationTime: Duration, + private val valueGetter: () -> T, +) { + private val expirationTimeMillis = expirationTime.toMillis() + private val lastUpdateTimeMillis = AtomicLong(0) + private val value = AtomicReference() + + fun getValue(): T { + val current = System.currentTimeMillis() + if (current - lastUpdateTimeMillis.get() > expirationTimeMillis) { + value.lazySet(valueGetter()) + lastUpdateTimeMillis.lazySet(current) + } + return value.get() + } +} diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index a387c8c7d8..8994d97959 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -1,9 +1,11 @@ package com.saveourtool.save.preprocessor +import com.saveourtool.save.configs.WebClientCustomizers import com.saveourtool.save.preprocessor.config.ConfigProperties import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Import import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.reactive.config.EnableWebFlux @@ -18,6 +20,7 @@ typealias StatusResponse = ResponseEntity @SpringBootApplication @EnableWebFlux @EnableConfigurationProperties(ConfigProperties::class) +@Import(WebClientCustomizers::class) class SavePreprocessor fun main(args: Array) { diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt index e087a807c1..5c1855c3e6 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/config/LocalDateTimeConfig.kt @@ -14,7 +14,6 @@ package com.saveourtool.save.preprocessor.config import com.saveourtool.save.utils.LocalDateTimeSerializer import com.fasterxml.jackson.databind.SerializationFeature -import com.saveourtool.save.configs.WebClientCustomizers import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.context.annotation.Bean @@ -29,7 +28,6 @@ import java.time.LocalDateTime import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule -import org.springframework.context.annotation.Import internal val json = Json { serializersModule = SerializersModule { @@ -38,7 +36,6 @@ internal val json = Json { } @Configuration -@Import(WebClientCustomizers::class) class LocalDateTimeConfig { @Bean fun jackson2ObjectMapperBuilderCustomizer() = Jackson2ObjectMapperBuilderCustomizer { jacksonObjectMapperBuilder: Jackson2ObjectMapperBuilder -> From 6f33194ecda4885239a5ad7976625f826d4684fe Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 14:32:35 +0300 Subject: [PATCH 17/57] Extension method for KubernetesAuthenticationUtils --- gradle/libs.versions.toml | 1 + .../save/backend/configs/WebSecurityConfig.kt | 13 ++---------- save-cloud-common/build.gradle.kts | 1 + .../security/KubernetesAuthenticationUtils.kt | 20 +++++++++++++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 157a54d58c..3c8c734628 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,6 +91,7 @@ spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-aut spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } spring-security-core = { module = "org.springframework.security:spring-security-core" } spring-security-web = { module = "org.springframework.security:spring-security-web" } +spring-security-config = { module = "org.springframework.security:spring-security-config" } spring-security-oauth2-client = { module = "org.springframework.security:spring-security-oauth2-client" } spring-security-test = { module = "org.springframework.security:spring-security-test" } spring-boot-gradle-plugin = { module = "org.springframework.boot:spring-boot-gradle-plugin", version.ref = "spring-boot" } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 2c533ef5ce..9a7585961f 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -10,6 +10,7 @@ import com.saveourtool.save.domain.Role import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils import com.saveourtool.save.spring.security.ServiceAccountAuthenticatingManager import com.saveourtool.save.spring.security.ServiceAccountTokenExtractorConverter +import com.saveourtool.save.spring.security.serviceAccountTokenAuthentication import com.saveourtool.save.v1 import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean @@ -151,17 +152,7 @@ class WebSecurityConfig( .pathMatchers("/internal/**") .authenticated() .and() - .addFilterBefore( - AuthenticationWebFilter(serviceAccountAuthenticatingManager).apply { - setServerAuthenticationConverter(serviceAccountTokenExtractorConverter) - }, - SecurityWebFiltersOrder.HTTP_BASIC - ) - .exceptionHandling { - it.authenticationEntryPoint( - HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) - ) - } + .serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager) .csrf() .disable() .logout() diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index d38b977b59..5bf5aac9d1 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -45,6 +45,7 @@ kotlin { implementation(project.dependencies.platform(libs.spring.boot.dependencies)) implementation(libs.spring.security.core) implementation(libs.spring.security.web) + implementation(libs.spring.security.config) implementation(libs.spring.web) implementation(libs.spring.webflux) implementation(libs.spring.boot) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index ec6bc4e393..abe7cd211c 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -10,11 +10,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatfo import org.springframework.boot.cloud.CloudPlatform import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +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.core.Authentication +import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken +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.stereotype.Component import org.springframework.web.server.ServerWebExchange @@ -26,6 +31,21 @@ import reactor.kotlin.core.publisher.toMono @Import(ServiceAccountTokenExtractorConverter::class, ServiceAccountAuthenticatingManager::class) open class KubernetesAuthenticationUtils +fun ServerHttpSecurity.serviceAccountTokenAuthentication( + serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, + serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, +): ServerHttpSecurity = addFilterBefore( + AuthenticationWebFilter(serviceAccountAuthenticatingManager).apply { + setServerAuthenticationConverter(serviceAccountTokenExtractorConverter) + }, + SecurityWebFiltersOrder.HTTP_BASIC + ) + .exceptionHandling { + it.authenticationEntryPoint( + HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) + ) + } + @Component @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { From 23bead958b5f5c1ba7a4bbc38b5e23fa5fe8569a Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 20 Oct 2022 14:41:47 +0300 Subject: [PATCH 18/57] Code style --- .../save/backend/configs/WebSecurityConfig.kt | 52 +++---- .../save/configs/WebClientCustomizers.kt | 39 ++++- .../security/KubernetesAuthenticationUtils.kt | 147 ++++++++++-------- 3 files changed, 141 insertions(+), 97 deletions(-) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 9a7585961f..150272045f 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -109,32 +109,6 @@ class WebSecurityConfig( defaultMethodSecurityExpressionHandler.setRoleHierarchy(roleHierarchy()) } - companion object { - /** - * These endpoints will have `permitAll` enabled on them. We can't selectively put `@PreAuthorize("permitAll")` in the code, - * because it won't allow us to configure authenticated access to all other endpoints by default. - * Or we can use custom AccessDecisionManager later. - */ - internal val publicEndpoints = listOf( - "/error", - // `CollectionView` is a public page - "/api/$v1/projects/not-deleted", - "/api/$v1/awesome-benchmarks", - "/api/$v1/check-git-connectivity-adaptor", - // `OrganizationView` is a public page - // fixme: when we will want to make organizations accessible for everyone, wi will need to add more endpoints here - "/api/$v1/organizations/**", - "/api/$v1/projects/get/projects-by-organization", - // `ContestListView` and `ContestView` are public pages - "/api/$v1/contests/*", - "/api/$v1/contests/active", - "/api/$v1/contests/finished", - "/api/$v1/contests/*/public-test", - "/api/$v1/contests/*/scores", - "/api/$v1/contests/*/*/best", - ) - } - @Profile("kubernetes") @Bean @Order(2) @@ -175,6 +149,32 @@ class WebSecurityConfig( .and() .build() } + + companion object { + /** + * These endpoints will have `permitAll` enabled on them. We can't selectively put `@PreAuthorize("permitAll")` in the code, + * because it won't allow us to configure authenticated access to all other endpoints by default. + * Or we can use custom AccessDecisionManager later. + */ + internal val publicEndpoints = listOf( + "/error", + // `CollectionView` is a public page + "/api/$v1/projects/not-deleted", + "/api/$v1/awesome-benchmarks", + "/api/$v1/check-git-connectivity-adaptor", + // `OrganizationView` is a public page + // fixme: when we will want to make organizations accessible for everyone, wi will need to add more endpoints here + "/api/$v1/organizations/**", + "/api/$v1/projects/get/projects-by-organization", + // `ContestListView` and `ContestView` are public pages + "/api/$v1/contests/*", + "/api/$v1/contests/active", + "/api/$v1/contests/finished", + "/api/$v1/contests/*/public-test", + "/api/$v1/contests/*/scores", + "/api/$v1/contests/*/*/best", + ) + } } @EnableWebFluxSecurity diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index 13364e3f7d..353eb1b7da 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -1,30 +1,45 @@ +/** + * Customization for spring `WebClient` + */ + package com.saveourtool.save.configs +import com.saveourtool.save.spring.security.SA_HEADER_NAME import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.getLogger + 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.web.reactive.function.client.ClientRequest import org.springframework.context.annotation.Bean import org.springframework.stereotype.Component +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 configration class that can be used to import all related [WebClientCustomizer] beans. + */ @Component class WebClientCustomizers { @Bean @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) + @Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_CLASS_ELEMENTS") fun serviceAccountTokenHeaderWebClientCustomizer() = ServiceAccountTokenHeaderWebClientCustomizer() - - private val logger = getLogger() } +/** + * A [WebClientCustomizer] that appends Kubernetes' ServiceAccount token as a custom header. + */ class ServiceAccountTokenHeaderWebClientCustomizer : WebClientCustomizer { + @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") + private val logger = getLogger() private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(5)) { val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() token @@ -33,25 +48,33 @@ class ServiceAccountTokenHeaderWebClientCustomizer : WebClientCustomizer { override fun customize(builder: WebClient.Builder) { builder.filter { request, next -> val token = wrapper.getValue() - logger.debug { "Appending `X-Service-Account-Token` header to the request ${request.method()} to ${request.url()}" } + logger.debug { "Appending `$SA_HEADER_NAME` header to the request ${request.method()} to ${request.url()}" } ClientRequest.from(request) - .header("X-Service-Account-Token", token) + .header(SA_HEADER_NAME, token) .build() .let(next::exchange) } } - - private val logger = getLogger() } +/** + * 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( expirationTime: Duration, private val valueGetter: () -> T, ) { private val expirationTimeMillis = expirationTime.toMillis() private val lastUpdateTimeMillis = AtomicLong(0) - private val value = AtomicReference() + private val value: AtomicReference = AtomicReference() + /** + * @return cached value or refreshes the value and returns it + */ fun getValue(): T { val current = System.currentTimeMillis() if (current - lastUpdateTimeMillis.get() > expirationTimeMillis) { diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index abe7cd211c..4da369c558 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -1,7 +1,12 @@ +/** + * Utilities to configure Kubernetes ServiceAccount token-based authentication in Spring Security. + */ + package com.saveourtool.save.spring.security 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 @@ -15,8 +20,8 @@ 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.core.Authentication 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.authentication.AuthenticationWebFilter import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint @@ -27,85 +32,101 @@ 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) open class KubernetesAuthenticationUtils -fun ServerHttpSecurity.serviceAccountTokenAuthentication( - serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, - serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, -): ServerHttpSecurity = addFilterBefore( - AuthenticationWebFilter(serviceAccountAuthenticatingManager).apply { - setServerAuthenticationConverter(serviceAccountTokenExtractorConverter) - }, - SecurityWebFiltersOrder.HTTP_BASIC - ) - .exceptionHandling { - it.authenticationEntryPoint( - HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) - ) - } - +/** + * 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 { - override fun convert(exchange: ServerWebExchange): Mono { - return Mono.justOrEmpty( - exchange.request.headers["X-Service-Account-Token"]?.firstOrNull() - ).map { token -> - logger.debug { "Starting to process `X-Service-Account-Token` of an incoming request" } - PreAuthenticatedAuthenticationToken("TokenSupplier", token) - } - } - + @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() + override fun convert(exchange: ServerWebExchange): Mono = Mono.justOrEmpty( + exchange.request.headers["X-Service-Account-Token"]?.firstOrNull() + ).map { token -> + logger.debug { "Starting to process `X-Service-Account-Token` of an incoming request" } + 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 { - override fun authenticate(authentication: Authentication): Mono { - return authentication.toMono() - .filter { it is PreAuthenticatedAuthenticationToken } - .map { - val token = it.credentials - @Language("yaml") - val tokenReview = """ - |apiVersion: authentication.k8s.io/v1 - |kind: TokenReview - |metadata: - | name: service-account-validity-check - | namespace: ${kubernetesClient.namespace} - |spec: - | token: $token - """.trimMargin() - 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 + @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") + private val logger = getLogger() + override fun authenticate(authentication: Authentication): Mono = 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(" ")}" } - .map { - with (authentication) { - UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities) - } + val response = kubernetesClient.resource(tokenReview).createOrReplace() as TokenReview + logger.debug { + "Got the following response from the API server:\n${ + Serialization.yamlMapper().writeValueAsString(response).prependIndent(" ") + }" } - .switchIfEmpty { - Mono.error { BadCredentialsException("Invalid token") } + 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 { + with(authentication) { + UsernamePasswordAuthenticationToken.authenticated(principal, credentials, authorities) } - } + } + .switchIfEmpty { + Mono.error { BadCredentialsException("Invalid token") } + } - private val logger = getLogger() + @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) + ) + } From 6b048b6ddac441451220ad0b281866abc974fb6a Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 21 Oct 2022 13:55:35 +0300 Subject: [PATCH 19/57] Apply `WebClientCustomizers` to all `WebClient`s; fix `!kubernetes` security config --- .../save/backend/configs/WebSecurityConfig.kt | 2 ++ .../save/backend/controllers/RunExecutionController.kt | 4 ++++ .../saveourtool/save/configs/WebClientCustomizers.kt | 2 +- .../saveourtool/save/spring/utils/WebClientUtils.kt | 10 ++++++++++ .../saveourtool/save/orchestrator/SaveOrchestrator.kt | 5 ++++- .../orchestrator/service/BackendAgentRepository.kt | 9 ++++++++- .../controllers/AwesomeBenchmarksDownloadController.kt | 8 +++++++- .../service/TestsPreprocessorToBackendBridge.kt | 7 +++---- .../kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt | 5 ++++- 9 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 150272045f..3f842d3b4e 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -147,6 +147,8 @@ class WebSecurityConfig( authorizeExchange().pathMatchers("/internal/**", "/actuator/**") .permitAll() .and() + .csrf() + .disable() .build() } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt index 02db748cd2..899b3bf3c1 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt @@ -21,8 +21,10 @@ import com.saveourtool.save.utils.switchIfEmptyToResponseException import com.saveourtool.save.v1 import com.fasterxml.jackson.databind.ObjectMapper +import com.saveourtool.save.spring.utils.applyAll import io.micrometer.core.instrument.MeterRegistry import org.slf4j.Logger +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity @@ -53,12 +55,14 @@ class RunExecutionController( private val meterRegistry: MeterRegistry, configProperties: ConfigProperties, objectMapper: ObjectMapper, + customizers: List, ) { private val webClientOrchestrator = WebClient.builder() .baseUrl(configProperties.orchestratorUrl) .codecs { it.defaultCodecs().multipartCodecs().encoder(Jackson2JsonEncoder(objectMapper)) } + .applyAll(customizers) .build() private val scheduler = Schedulers.boundedElastic() diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index 353eb1b7da..553248d1de 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicReference import kotlin.io.path.readText /** - * A configration class that can be used to import all related [WebClientCustomizer] beans. + * A configuration class that can be used to import all related [WebClientCustomizer] beans. */ @Component class WebClientCustomizers { diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt new file mode 100644 index 0000000000..fd52483591 --- /dev/null +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt @@ -0,0 +1,10 @@ +package com.saveourtool.save.spring.utils + +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer +import org.springframework.web.reactive.function.client.WebClient + +fun WebClient.Builder.applyAll(customizers: Iterable): WebClient.Builder = apply { + customizers.forEach { + it.customize(this) + } +} diff --git a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt index a01191fe57..54421424e6 100644 --- a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt +++ b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt @@ -1,13 +1,16 @@ package com.saveourtool.save.orchestrator +import com.saveourtool.save.configs.WebClientCustomizers import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Import /** * An entrypoint for spring boot for save-orchestrator */ @SpringBootApplication -open class SaveOrchestrator +@Import(WebClientCustomizers::class) +class SaveOrchestrator fun main(args: Array) { SpringApplication.run(SaveOrchestrator::class.java, *args) diff --git a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt index 2f06c2363d..d0d6eaf4a2 100644 --- a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt +++ b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt @@ -8,9 +8,11 @@ import com.saveourtool.save.entities.AgentStatusesForExecution import com.saveourtool.save.execution.ExecutionStatus import com.saveourtool.save.execution.ExecutionUpdateDto import com.saveourtool.save.orchestrator.config.ConfigProperties +import com.saveourtool.save.spring.utils.applyAll import com.saveourtool.save.test.TestBatch import com.saveourtool.save.utils.* import org.slf4j.Logger +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.http.ResponseEntity import org.springframework.stereotype.Component @@ -26,8 +28,13 @@ internal typealias BodilessResponseEntity = ResponseEntity @Component class BackendAgentRepository( configProperties: ConfigProperties, + customizers: List, ) : AgentRepository { - private val webClientBackend = WebClient.create(configProperties.backendUrl) + private val webClientBackend = WebClient.builder() + .baseUrl(configProperties.backendUrl) + .applyAll(customizers) + .build() + override fun getInitConfig(containerId: String): Mono = webClientBackend .get() .uri("/agents/get-init-config?containerId=$containerId") diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt index 758f0e272f..44ec719859 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt @@ -7,6 +7,7 @@ import com.saveourtool.save.preprocessor.service.GitPreprocessorService import com.saveourtool.save.preprocessor.utils.* import com.akuleshov7.ktoml.file.TomlFileReader +import com.saveourtool.save.spring.utils.applyAll import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -24,6 +25,7 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.absolutePathString import kotlin.io.path.div import kotlinx.serialization.serializer +import org.springframework.boot.web.reactive.function.client.WebClientCustomizer /** * A Spring controller for git project downloading @@ -35,8 +37,12 @@ import kotlinx.serialization.serializer class AwesomeBenchmarksDownloadController( private val configProperties: ConfigProperties, private val gitPreprocessorService: GitPreprocessorService, + customizers: List, ) { - private val webClientBackend = WebClient.create(configProperties.backend) + private val webClientBackend = WebClient.builder() + .baseUrl(configProperties.backend) + .applyAll(customizers) + .build() /** * Controller to download standard test suites diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt index e841f96766..791fdebf88 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt @@ -3,6 +3,7 @@ package com.saveourtool.save.preprocessor.service import com.saveourtool.save.entities.* import com.saveourtool.save.preprocessor.EmptyResponse import com.saveourtool.save.preprocessor.config.ConfigProperties +import com.saveourtool.save.spring.utils.applyAll import com.saveourtool.save.test.TestDto import com.saveourtool.save.testsuite.* import com.saveourtool.save.utils.debug @@ -25,13 +26,11 @@ import java.time.Instant @Service class TestsPreprocessorToBackendBridge( configProperties: ConfigProperties, - kotlinSerializationWebClientCustomizer: WebClientCustomizer, - serviceAccountTokenHeaderWebClientCustomizer: WebClientCustomizer, + customizers: List, ) { private val webClientBackend = WebClient.builder() .baseUrl(configProperties.backend) - .apply(kotlinSerializationWebClientCustomizer::customize) - .apply(serviceAccountTokenHeaderWebClientCustomizer::customize) + .applyAll(customizers) .build() /** diff --git a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt index 5f82d10705..4a15c0376c 100644 --- a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt +++ b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt @@ -1,13 +1,16 @@ package com.saveourtool.save.sandbox +import com.saveourtool.save.configs.WebClientCustomizers import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Import /** * An entrypoint for spring boot for save-sandbox */ @SpringBootApplication -open class SaveSandbox +@Import(WebClientCustomizers::class) +class SaveSandbox fun main(args: Array) { SpringApplication.run(SaveSandbox::class.java, *args) From bdf937a479f4b9b30ecde63e4ae778f236f43dab Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 21 Oct 2022 13:56:10 +0300 Subject: [PATCH 20/57] Protect orchestrator and sandbox with SA Token Authorization --- save-orchestrator-common/build.gradle.kts | 1 + .../orchestrator/config/WebSecurityConfig.kt | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt diff --git a/save-orchestrator-common/build.gradle.kts b/save-orchestrator-common/build.gradle.kts index 14beeb11b2..a617c64dcf 100644 --- a/save-orchestrator-common/build.gradle.kts +++ b/save-orchestrator-common/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(libs.zip4j) implementation(libs.fabric8.kubernetes.client) implementation(libs.spring.kafka) + implementation(libs.spring.boot.starter.security) testImplementation(projects.testUtils) testImplementation(libs.fabric8.kubernetes.server.mock) } diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt new file mode 100644 index 0000000000..45cbef793d --- /dev/null +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt @@ -0,0 +1,43 @@ +package com.saveourtool.save.orchestrator.config + +import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils +import com.saveourtool.save.spring.security.ServiceAccountAuthenticatingManager +import com.saveourtool.save.spring.security.ServiceAccountTokenExtractorConverter +import com.saveourtool.save.spring.security.serviceAccountTokenAuthentication +import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform +import org.springframework.boot.cloud.CloudPlatform +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import +import org.springframework.core.annotation.Order +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher +import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers + +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +@Import(KubernetesAuthenticationUtils::class) +@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +class WebSecurityConfig { + @Bean + fun securityWebFilterChain( + http: ServerHttpSecurity, + serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, + serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, + ): SecurityWebFilterChain = http + .securityMatcher( + AndServerWebExchangeMatcher( + ServerWebExchangeMatchers.anyExchange(), + NegatedServerWebExchangeMatcher( + ServerWebExchangeMatchers.pathMatchers("/actuator/**") + ) + ) + ) + .serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager) + .csrf() + .disable() + .build() +} \ No newline at end of file From 20c71712392af6c880e9643fc14997a1983dbe3c Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 24 Oct 2022 08:07:49 +0300 Subject: [PATCH 21/57] Code style --- .../saveourtool/save/spring/utils/WebClientUtils.kt | 6 ++++++ .../save/orchestrator/config/WebSecurityConfig.kt | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt index fd52483591..0943865b1e 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt @@ -3,6 +3,12 @@ package com.saveourtool.save.spring.utils import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.web.reactive.function.client.WebClient +/** + * Applies all [WebClientCustomizer]s from [customizers] to [this] [WebClient.Builder]. + * + * @param customizers [WebClientCustomizer]s to be applied + * @return the modified builder + */ fun WebClient.Builder.applyAll(customizers: Iterable): WebClient.Builder = apply { customizers.forEach { it.customize(this) diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt index 45cbef793d..9ef9aefcd5 100644 --- a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt @@ -8,7 +8,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatfo import org.springframework.boot.cloud.CloudPlatform import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Import -import org.springframework.core.annotation.Order import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity @@ -17,12 +16,21 @@ import org.springframework.security.web.server.util.matcher.AndServerWebExchange import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers +/** + * Configuration class to set up Spring Security + */ @EnableWebFluxSecurity @EnableReactiveMethodSecurity @Import(KubernetesAuthenticationUtils::class) @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) class WebSecurityConfig { + /** + * Configures spring-security to use ServiceAccount based authentication. + * Beans [serviceAccountTokenExtractorConverter] and [serviceAccountAuthenticatingManager] need to be passed into + * [serviceAccountTokenAuthentication]. + */ @Bean + @Suppress("KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") fun securityWebFilterChain( http: ServerHttpSecurity, serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, @@ -40,4 +48,4 @@ class WebSecurityConfig { .csrf() .disable() .build() -} \ No newline at end of file +} From 39849e7f6f58d413495ad19d8c8bd3a0fee3dd68 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 27 Oct 2022 15:12:21 +0300 Subject: [PATCH 22/57] [skip ci] Fix bean name clash --- .../saveourtool/save/orchestrator/config/WebSecurityConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt index 9ef9aefcd5..17e24eef0c 100644 --- a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt @@ -23,13 +23,13 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat @EnableReactiveMethodSecurity @Import(KubernetesAuthenticationUtils::class) @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -class WebSecurityConfig { +class KubernetesServiceAccountWebSecurityConfig { /** * Configures spring-security to use ServiceAccount based authentication. * Beans [serviceAccountTokenExtractorConverter] and [serviceAccountAuthenticatingManager] need to be passed into * [serviceAccountTokenAuthentication]. */ - @Bean + @Bean(name = ["kubernetesServiceAccountSecurityWebFilterChain"]) @Suppress("KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") fun securityWebFilterChain( http: ServerHttpSecurity, From e53392dd8a137c7e9719c2057beea3ce7b94e6e6 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 9 Nov 2022 08:27:44 +0300 Subject: [PATCH 23/57] [skip ci] WIP: Restore security-related beans in a configuration class --- .../save/backend/configs/WebSecurityConfig.kt | 2 + .../security/KubernetesAuthenticationUtils.kt | 49 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index ed697a83bf..67d20e7109 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -10,6 +10,7 @@ import com.saveourtool.save.authservice.repository.AuthenticationUserRepository import com.saveourtool.save.authservice.security.ConvertingAuthenticationManager import com.saveourtool.save.authservice.security.CustomAuthenticationBasicConverter import com.saveourtool.save.authservice.service.AuthenticationUserDetailsService +import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils import org.springframework.context.annotation.Import import org.springframework.context.annotation.Profile @@ -26,6 +27,7 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux CustomAuthenticationBasicConverter::class, AuthenticationUserDetailsService::class, AuthenticationUserRepository::class, + KubernetesAuthenticationUtils::class, ) @Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") class BackendWebSecurityConfig diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 4da369c558..8aed99b7f6 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -13,8 +13,11 @@ 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.context.annotation.Profile +import org.springframework.core.annotation.Order import org.springframework.http.HttpStatus import org.springframework.security.authentication.BadCredentialsException import org.springframework.security.authentication.ReactiveAuthenticationManager @@ -23,6 +26,7 @@ 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 @@ -40,7 +44,50 @@ const val SA_HEADER_NAME = "X-Service-Account-Token" */ @Configuration @Import(ServiceAccountTokenExtractorConverter::class, ServiceAccountAuthenticatingManager::class) -open class KubernetesAuthenticationUtils +open class KubernetesAuthenticationUtils { + @Profile("kubernetes") + @Bean + @Order(2) + open fun internalSecuredSecurityChain( + http: ServerHttpSecurity, + serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, + serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, + ): SecurityWebFilterChain = http.run { + 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("/internal/**") + .authenticated() + .and() + .serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager) + .csrf() + .disable() + .logout() + .disable() + .formLogin() + .disable() + .build() + } + + @Profile("!kubernetes") + @Bean + @Order(2) + open fun internalInsecureSecurityChain( + http: ServerHttpSecurity + ): SecurityWebFilterChain = http.run { + // All `/internal/**` and `/actuator/**` requests should be sent only from internal network, + // they are not proxied from gateway. + authorizeExchange().pathMatchers("/internal/**", "/actuator/**") + .permitAll() + .and() + .csrf() + .disable() + .build() + } +} /** * A [ServerAuthenticationConverter] that attempts to convert a [ServerWebExchange] to an [Authentication] From 7a04db0c01b9702be5aba0d54211674971aac65c Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 9 Nov 2022 08:54:41 +0300 Subject: [PATCH 24/57] [skip ci] Cleanup after merge --- .../save/backend/utils/ConvertingAuthenticationManager.kt | 0 .../com/saveourtool/save/spring/utils/WebClientUtils.kt | 4 ++++ 2 files changed, 4 insertions(+) delete mode 100644 save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/utils/ConvertingAuthenticationManager.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt index 0943865b1e..b45fce20dd 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/utils/WebClientUtils.kt @@ -1,3 +1,7 @@ +/** + * Utilities for spring WebClient + */ + package com.saveourtool.save.spring.utils import org.springframework.boot.web.reactive.function.client.WebClientCustomizer From 15ae7e3fd09316b74945094d562b3e1ace324037 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 9 Nov 2022 09:16:02 +0300 Subject: [PATCH 25/57] [skip ci] Resolve conflict between beans --- .../authservice/security/ConvertingAuthenticationManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/security/ConvertingAuthenticationManager.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/security/ConvertingAuthenticationManager.kt index 5cc040b1ef..e7e4cc0f74 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/security/ConvertingAuthenticationManager.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/security/ConvertingAuthenticationManager.kt @@ -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 @@ -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 { From 92c4e01c738c033f61793a72106ed4fa870a5f7d Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 9 Nov 2022 12:08:36 +0300 Subject: [PATCH 26/57] [skip ci] Disable spring-security autoconfiguration on preprocessor; make default WebSecurityConfig have the lowest precedence Since it captures all requests not handled by other matchers --- .../save/authservice/config/WebSecurityConfig.kt | 3 +++ .../saveourtool/save/preprocessor/SavePreprocessor.kt | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt index 0816b82fd7..16b592e2d0 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt @@ -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 @@ -35,6 +37,7 @@ class WebSecurityConfig( @Autowired private var defaultMethodSecurityExpressionHandler: DefaultMethodSecurityExpressionHandler ) { @Bean + @Order(Ordered.LOWEST_PRECEDENCE) fun securityWebFilterChain( http: ServerHttpSecurity ): SecurityWebFilterChain = http.run { diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index 8994d97959..6494ba188b 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -3,7 +3,11 @@ package com.saveourtool.save.preprocessor import com.saveourtool.save.configs.WebClientCustomizers import com.saveourtool.save.preprocessor.config.ConfigProperties import org.springframework.boot.SpringApplication +import org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Import import org.springframework.http.HttpStatus @@ -17,7 +21,11 @@ typealias StatusResponse = ResponseEntity /** * An entrypoint for spring for save-preprocessor */ -@SpringBootApplication +@SpringBootApplication(exclude = [ + ReactiveSecurityAutoConfiguration::class, + ReactiveUserDetailsServiceAutoConfiguration::class, + ReactiveManagementWebSecurityAutoConfiguration::class, +]) @EnableWebFlux @EnableConfigurationProperties(ConfigProperties::class) @Import(WebClientCustomizers::class) From cd60fe4b2e5d22a46189947f9e9533d751cad17c Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Nov 2022 07:58:01 +0300 Subject: [PATCH 27/57] [skip ci] Cleanup --- .../templates/service-accounts.yaml | 1 + .../security/KubernetesAuthenticationUtils.kt | 5 ++-- .../orchestrator/config/WebSecurityConfig.kt | 27 +------------------ 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/save-cloud-charts/save-cloud/templates/service-accounts.yaml b/save-cloud-charts/save-cloud/templates/service-accounts.yaml index 67eb9f3730..9ed0673707 100644 --- a/save-cloud-charts/save-cloud/templates/service-accounts.yaml +++ b/save-cloud-charts/save-cloud/templates/service-accounts.yaml @@ -31,6 +31,7 @@ roleRef: --- +# Give access to `TokenReview` to be able to validate incoming ServiceAccount tokens apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 8aed99b7f6..c5c82810b1 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -16,7 +16,6 @@ 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.context.annotation.Profile import org.springframework.core.annotation.Order import org.springframework.http.HttpStatus import org.springframework.security.authentication.BadCredentialsException @@ -45,7 +44,7 @@ const val SA_HEADER_NAME = "X-Service-Account-Token" @Configuration @Import(ServiceAccountTokenExtractorConverter::class, ServiceAccountAuthenticatingManager::class) open class KubernetesAuthenticationUtils { - @Profile("kubernetes") + @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Bean @Order(2) open fun internalSecuredSecurityChain( @@ -72,7 +71,7 @@ open class KubernetesAuthenticationUtils { .build() } - @Profile("!kubernetes") + @ConditionalOnCloudPlatform(CloudPlatform.NONE) @Bean @Order(2) open fun internalInsecureSecurityChain( diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt index 17e24eef0c..34f1221521 100644 --- a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt @@ -23,29 +23,4 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat @EnableReactiveMethodSecurity @Import(KubernetesAuthenticationUtils::class) @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -class KubernetesServiceAccountWebSecurityConfig { - /** - * Configures spring-security to use ServiceAccount based authentication. - * Beans [serviceAccountTokenExtractorConverter] and [serviceAccountAuthenticatingManager] need to be passed into - * [serviceAccountTokenAuthentication]. - */ - @Bean(name = ["kubernetesServiceAccountSecurityWebFilterChain"]) - @Suppress("KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") - fun securityWebFilterChain( - http: ServerHttpSecurity, - serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, - serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, - ): SecurityWebFilterChain = http - .securityMatcher( - AndServerWebExchangeMatcher( - ServerWebExchangeMatchers.anyExchange(), - NegatedServerWebExchangeMatcher( - ServerWebExchangeMatchers.pathMatchers("/actuator/**") - ) - ) - ) - .serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager) - .csrf() - .disable() - .build() -} +class KubernetesServiceAccountWebSecurityConfig From b20dda2259e6be72f6732684e0a0be255dce514b Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Nov 2022 08:03:47 +0300 Subject: [PATCH 28/57] Code style --- .../save/configs/WebClientCustomizers.kt | 7 ++++-- .../security/KubernetesAuthenticationUtils.kt | 24 ++++++++++++++++--- ...ernetesServiceAccountWebSecurityConfig.kt} | 9 ------- 3 files changed, 26 insertions(+), 14 deletions(-) rename save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/{WebSecurityConfig.kt => KubernetesServiceAccountWebSecurityConfig.kt} (52%) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index 553248d1de..2ec265c20a 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -7,6 +7,7 @@ package com.saveourtool.save.configs import com.saveourtool.save.spring.security.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 @@ -37,10 +38,12 @@ class WebClientCustomizers { /** * A [WebClientCustomizer] that appends Kubernetes' ServiceAccount token as a custom header. */ -class ServiceAccountTokenHeaderWebClientCustomizer : WebClientCustomizer { +class ServiceAccountTokenHeaderWebClientCustomizer( + @Value("\${com.saveourtool.cloud.kubernetes.sa-token.expiration.minutes:5}") private val expirationTimeMinutes: Long, +) : WebClientCustomizer { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() - private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(5)) { + private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(expirationTimeMinutes)) { val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() token } diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index c5c82810b1..5d15957abd 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -42,11 +42,21 @@ const val SA_HEADER_NAME = "X-Service-Account-Token" * to work with Kubernetes ServiceAccount tokens. */ @Configuration -@Import(ServiceAccountTokenExtractorConverter::class, ServiceAccountAuthenticatingManager::class) +@Import( + ServiceAccountTokenExtractorConverter::class, + ServiceAccountAuthenticatingManager::class, +) +@Suppress( + "AVOID_USING_UTILITY_CLASS", // Spring beans need to be declared inside `@Configuration` class. +) open class KubernetesAuthenticationUtils { @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Bean @Order(2) + @Suppress( + "MISSING_KDOC_CLASS_ELEMENTS", + "MISSING_KDOC_ON_FUNCTION", + ) open fun internalSecuredSecurityChain( http: ServerHttpSecurity, serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, @@ -71,14 +81,22 @@ open class KubernetesAuthenticationUtils { .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", + ) open fun internalInsecureSecurityChain( http: ServerHttpSecurity ): SecurityWebFilterChain = http.run { - // All `/internal/**` and `/actuator/**` requests should be sent only from internal network, - // they are not proxied from gateway. authorizeExchange().pathMatchers("/internal/**", "/actuator/**") .permitAll() .and() diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt similarity index 52% rename from save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt rename to save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt index 34f1221521..0ca2c6ef6a 100644 --- a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/WebSecurityConfig.kt +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt @@ -1,20 +1,11 @@ package com.saveourtool.save.orchestrator.config import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils -import com.saveourtool.save.spring.security.ServiceAccountAuthenticatingManager -import com.saveourtool.save.spring.security.ServiceAccountTokenExtractorConverter -import com.saveourtool.save.spring.security.serviceAccountTokenAuthentication import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform import org.springframework.boot.cloud.CloudPlatform -import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Import import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity -import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.web.server.SecurityWebFilterChain -import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers /** * Configuration class to set up Spring Security From eca3464db1df90e8e887720ded04048d190b2593 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Nov 2022 08:21:45 +0300 Subject: [PATCH 29/57] Fix compilation --- .../com/saveourtool/save/configs/WebClientCustomizers.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index 2ec265c20a..c536465ced 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -32,14 +32,18 @@ class WebClientCustomizers { @Bean @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_CLASS_ELEMENTS") - fun serviceAccountTokenHeaderWebClientCustomizer() = ServiceAccountTokenHeaderWebClientCustomizer() + 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( - @Value("\${com.saveourtool.cloud.kubernetes.sa-token.expiration.minutes:5}") private val expirationTimeMinutes: Long, + expirationTimeMinutes: Long, ) : WebClientCustomizer { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() From 45a0c3bf492700f24af057d411008f7980e1f54a Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Nov 2022 08:29:24 +0300 Subject: [PATCH 30/57] Fix compilation --- .../save/orchestrator/service/BackendAgentRepository.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt index 612b40e96f..a6072f1267 100644 --- a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt +++ b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/service/BackendAgentRepository.kt @@ -8,9 +8,7 @@ import com.saveourtool.save.entities.AgentStatusDto import com.saveourtool.save.entities.AgentStatusesForExecution import com.saveourtool.save.execution.ExecutionStatus import com.saveourtool.save.execution.ExecutionUpdateDto -import com.saveourtool.save.orchestrator.config.ConfigProperties import com.saveourtool.save.spring.utils.applyAll -import com.saveourtool.save.test.TestBatch import com.saveourtool.save.utils.* import org.slf4j.Logger @@ -29,12 +27,11 @@ internal typealias BodilessResponseEntity = ResponseEntity */ @Component class BackendAgentRepository( - configProperties: ConfigProperties, @Value("\${orchestrator.backend-url}") private val backendUrl: String, customizers: List, ) : AgentRepository { private val webClientBackend = WebClient.builder() - .baseUrl(configProperties.backendUrl) + .baseUrl(backendUrl) .applyAll(customizers) .build() From 3c1ff882248475d56d777f84d9fdf140936c6087 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 10 Nov 2022 19:09:45 +0300 Subject: [PATCH 31/57] `@Component` -> `@Configuration`; remove duplicated imports --- .../com/saveourtool/save/configs/WebClientCustomizers.kt | 8 ++++---- .../controllers/AwesomeBenchmarksDownloadController.kt | 2 -- .../service/TestsPreprocessorToBackendBridge.kt | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt index c536465ced..e9dddc0e62 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt @@ -13,7 +13,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatfo import org.springframework.boot.cloud.CloudPlatform import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.context.annotation.Bean -import org.springframework.stereotype.Component +import org.springframework.context.annotation.Configuration import org.springframework.web.reactive.function.client.ClientRequest import org.springframework.web.reactive.function.client.WebClient @@ -27,12 +27,12 @@ import kotlin.io.path.readText /** * A configuration class that can be used to import all related [WebClientCustomizer] beans. */ -@Component -class WebClientCustomizers { +@Configuration +open class WebClientCustomizers { @Bean @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_CLASS_ELEMENTS") - fun serviceAccountTokenHeaderWebClientCustomizer( + open fun serviceAccountTokenHeaderWebClientCustomizer( @Value("\${com.saveourtool.cloud.kubernetes.sa-token.expiration.minutes:5}") expirationTimeMinutes: Long ) = ServiceAccountTokenHeaderWebClientCustomizer(expirationTimeMinutes) } diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt index 1d5cf26903..3cf659000e 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/controllers/AwesomeBenchmarksDownloadController.kt @@ -8,7 +8,6 @@ import com.saveourtool.save.preprocessor.utils.* import com.saveourtool.save.spring.utils.applyAll import com.akuleshov7.ktoml.file.TomlFileReader -import com.saveourtool.save.spring.utils.applyAll import org.slf4j.LoggerFactory import org.springframework.boot.web.reactive.function.client.WebClientCustomizer import org.springframework.http.HttpStatus @@ -27,7 +26,6 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.absolutePathString import kotlin.io.path.div import kotlinx.serialization.serializer -import org.springframework.boot.web.reactive.function.client.WebClientCustomizer /** * A Spring controller for git project downloading diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt index ed46e4c79c..15e070dd61 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestsPreprocessorToBackendBridge.kt @@ -21,7 +21,7 @@ import reactor.core.publisher.Mono import java.time.Instant /** - * A bridge from preprocessor to backend (rest api wrapper) + * A bridge from preprocesor to backend (rest api wrapper) */ @Service class TestsPreprocessorToBackendBridge( From fd0d57aade6964f7f4f93b8573a0c6cf020a8b1d Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 07:14:18 +0300 Subject: [PATCH 32/57] Increase logging level; minor refactoring --- save-cloud-charts/save-cloud/templates/backend-configmap.yaml | 1 + .../save-cloud/templates/preprocessor-configmap.yaml | 1 + .../save/spring/security/KubernetesAuthenticationUtils.kt | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml index e6454ae55d..401630c020 100644 --- a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml @@ -11,3 +11,4 @@ data: management.server.port={{ .Values.backend.managementPort }} spring.datasource.url=${spring.datasource.backend-url} logging.level.org.springframework.cloud=DEBUG + logging.level.com.saveourtool.save.spring.security=DEBUG diff --git a/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml b/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml index a192b05872..d494033400 100644 --- a/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/preprocessor-configmap.yaml @@ -9,3 +9,4 @@ data: server.shutdown=graceful management.endpoints.web.exposure.include=* management.server.port={{ .Values.preprocessor.managementPort }} + logging.level.com.saveourtool.save.configs.ServiceAccountTokenHeaderWebClientCustomizer=DEBUG diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 5d15957abd..8c94f11f0c 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -116,9 +116,9 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() override fun convert(exchange: ServerWebExchange): Mono = Mono.justOrEmpty( - exchange.request.headers["X-Service-Account-Token"]?.firstOrNull() +` exchange.request.headers[SA_HEADER_NAME]?.firstOrNull() ).map { token -> - logger.debug { "Starting to process `X-Service-Account-Token` of an incoming request" } + logger.debug { "Starting to process `$SA_HEADER_NAME` of an incoming request" } PreAuthenticatedAuthenticationToken("TokenSupplier", token) } } From fee4a56dbcc2f86422397bdea4a726112fd5df59 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 08:48:39 +0300 Subject: [PATCH 33/57] Fix typo --- .../save/spring/security/KubernetesAuthenticationUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 8c94f11f0c..4728f7805a 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -116,7 +116,7 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() override fun convert(exchange: ServerWebExchange): Mono = Mono.justOrEmpty( -` exchange.request.headers[SA_HEADER_NAME]?.firstOrNull() + exchange.request.headers[SA_HEADER_NAME]?.firstOrNull() ).map { token -> logger.debug { "Starting to process `$SA_HEADER_NAME` of an incoming request" } PreAuthenticatedAuthenticationToken("TokenSupplier", token) From 0e2ee9be1eb58f57148abaae53b8466993e62463 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 09:38:57 +0300 Subject: [PATCH 34/57] Use `securityMatcher`s to glue together multiple security chains --- .../authservice/config/WebSecurityConfig.kt | 19 ++++++++++++++++--- .../security/KubernetesAuthenticationUtils.kt | 13 +++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt index 16b592e2d0..7a12e75ce8 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt @@ -25,13 +25,20 @@ 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 @@ -41,7 +48,11 @@ class WebSecurityConfig( 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 @@ -123,7 +134,9 @@ class NoopWebSecurityConfig { @Bean fun securityWebFilterChain( http: ServerHttpSecurity - ): SecurityWebFilterChain = http.authorizeExchange() + ): SecurityWebFilterChain = http + .securityMatcher(ServerWebExchangeMatchers.anyExchange()) + .authorizeExchange() .anyExchange() .permitAll() .and() diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 4728f7805a..694bb636ee 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -29,6 +29,7 @@ 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.ServerWebExchangeMatchers import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono @@ -62,7 +63,11 @@ open class KubernetesAuthenticationUtils { serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, ): SecurityWebFilterChain = http.run { - authorizeExchange().pathMatchers("/actuator/**") + securityMatcher( + ServerWebExchangeMatchers.pathMatchers("/actuator/**", "/internal/**") + ) + .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() @@ -97,7 +102,11 @@ open class KubernetesAuthenticationUtils { open fun internalInsecureSecurityChain( http: ServerHttpSecurity ): SecurityWebFilterChain = http.run { - authorizeExchange().pathMatchers("/internal/**", "/actuator/**") + securityMatcher( + ServerWebExchangeMatchers.pathMatchers("/internal/**", "/actuator/**") + ) + .authorizeExchange() + .pathMatchers("/internal/**", "/actuator/**") .permitAll() .and() .csrf() From c1a97c055c9690bef1001f83dc95011a6daa6a86 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 09:46:11 +0300 Subject: [PATCH 35/57] Import `WebClientCustomizers` in backend --- .../main/kotlin/com/saveourtool/save/backend/SaveApplication.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt index 96ae7b325e..17109f08c2 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt @@ -21,6 +21,7 @@ typealias StringList = List */ @SpringBootApplication @EnableConfigurationProperties(ConfigProperties::class) +@Import(WebClientCustomizers::class) class SaveApplication fun main(args: Array) { From a2da43b34fc3286d154b143c8f841939785c451b Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 09:46:34 +0300 Subject: [PATCH 36/57] Bind `orchestrator-sa` ServiceAccount to `microservice` ClusterRole --- .../templates/orchestrator-service-account.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/save-cloud-charts/save-cloud/templates/orchestrator-service-account.yaml b/save-cloud-charts/save-cloud/templates/orchestrator-service-account.yaml index 6e3da3c1bb..ca5a5afb1c 100644 --- a/save-cloud-charts/save-cloud/templates/orchestrator-service-account.yaml +++ b/save-cloud-charts/save-cloud/templates/orchestrator-service-account.yaml @@ -34,3 +34,19 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: jobs-executor + +--- + +# Bind orchestrator-sa to ClusterRole required for TokenReview access +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: orchestrator-microservice-role-binding +subjects: + - kind: ServiceAccount + name: orchestrator-sa + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: microservice From d7c18a89072c20bcf474be3be7997fe6185c0baa Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 16:49:16 +0300 Subject: [PATCH 37/57] Improved logging --- .../save/spring/security/KubernetesAuthenticationUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 694bb636ee..1821463592 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -127,7 +127,7 @@ class ServiceAccountTokenExtractorConverter : ServerAuthenticationConverter { override fun convert(exchange: ServerWebExchange): Mono = Mono.justOrEmpty( exchange.request.headers[SA_HEADER_NAME]?.firstOrNull() ).map { token -> - logger.debug { "Starting to process `$SA_HEADER_NAME` of an incoming request" } + logger.debug { "Starting to process `$SA_HEADER_NAME` of an incoming request [${exchange.request.method} ${exchange.request.uri}]" } PreAuthenticatedAuthenticationToken("TokenSupplier", token) } } From 105231c0ab039f2b32282863cd3a8c2f774a87f6 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 16:57:37 +0300 Subject: [PATCH 38/57] Different path matcher for k8s-secured endpoints --- .../save/spring/security/KubernetesAuthenticationUtils.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt index 1821463592..69d3196695 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt @@ -29,6 +29,7 @@ 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 @@ -64,7 +65,9 @@ open class KubernetesAuthenticationUtils { serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, ): SecurityWebFilterChain = http.run { securityMatcher( - ServerWebExchangeMatchers.pathMatchers("/actuator/**", "/internal/**") + NegatedServerWebExchangeMatcher( + ServerWebExchangeMatchers.pathMatchers("/api/**", "/sandbox/api/**") + ) ) .authorizeExchange() .pathMatchers("/actuator/**") @@ -73,7 +76,7 @@ open class KubernetesAuthenticationUtils { .permitAll() .and() .authorizeExchange() - .pathMatchers("/internal/**") + .pathMatchers("/**") .authenticated() .and() .serviceAccountTokenAuthentication(serviceAccountTokenExtractorConverter, serviceAccountAuthenticatingManager) From 7a390ea39dba46b4294f6da45de170a5195a087b Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 11 Nov 2022 17:10:31 +0300 Subject: [PATCH 39/57] Helm: mount SA token to orchestrator; add missing property in backend-configmap.yaml --- save-cloud-charts/save-cloud/templates/backend-configmap.yaml | 1 + .../save-cloud/templates/orchestrator-deployment.yaml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml index 401630c020..7a8ca68a97 100644 --- a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml @@ -6,6 +6,7 @@ data: application.properties: | backend.preprocessor-url=http://preprocessor backend.orchestrator-url=http://orchestrator + backend.agent-settings.backend-url=http://{{ .Values.backend.name }} server.shutdown=graceful management.endpoints.web.exposure.include=* management.server.port={{ .Values.backend.managementPort }} diff --git a/save-cloud-charts/save-cloud/templates/orchestrator-deployment.yaml b/save-cloud-charts/save-cloud/templates/orchestrator-deployment.yaml index d13133ff71..f81a08eece 100644 --- a/save-cloud-charts/save-cloud/templates/orchestrator-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/orchestrator-deployment.yaml @@ -47,6 +47,7 @@ spec: fieldPath: spec.nodeName volumeMounts: - {{ include "spring-boot.config-volume-mount" . | indent 14 | trim }} + - {{ include "spring-boot.sa-token-mount" . | indent 14 | trim }} {{- include "spring-boot.management" .Values.orchestrator | nindent 10 }} resources: limits: @@ -55,3 +56,4 @@ spec: memory: 600M volumes: - {{ include "spring-boot.config-volume" (dict "service" .Values.orchestrator) | indent 10 | trim }} + - {{ include "spring-boot.sa-token-volume" (dict "service" .Values.backend) | indent 10 | trim }} From 4844e9bd8411c2396d603aebd4a9a5894a6a98b8 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 23 Nov 2022 13:42:50 +0300 Subject: [PATCH 40/57] Move k8s-security-related classes to authService module --- authentication-service/build.gradle.kts | 5 +++++ .../authservice/config/SecurityWebClientCustomizers.kt | 6 +++--- .../authservice/utils}/KubernetesAuthenticationUtils.kt | 8 ++++---- .../com/saveourtool/save/backend/SaveApplication.kt | 3 ++- .../saveourtool/save/backend/configs/WebSecurityConfig.kt | 2 +- .../save-cloud/templates/backend-configmap.yaml | 2 +- save-cloud-common/build.gradle.kts | 5 ----- save-orchestrator-common/build.gradle.kts | 1 + .../config/KubernetesServiceAccountWebSecurityConfig.kt | 2 +- .../com/saveourtool/save/orchestrator/SaveOrchestrator.kt | 4 ++-- .../com/saveourtool/save/preprocessor/SavePreprocessor.kt | 4 ++-- .../kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt | 4 ++-- 12 files changed, 24 insertions(+), 22 deletions(-) rename save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt => authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt (95%) rename {save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security => authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils}/KubernetesAuthenticationUtils.kt (98%) diff --git a/authentication-service/build.gradle.kts b/authentication-service/build.gradle.kts index e673040977..6a2a281547 100644 --- a/authentication-service/build.gradle.kts +++ b/authentication-service/build.gradle.kts @@ -25,6 +25,11 @@ dependencies { implementation(projects.saveCloudCommon) implementation(libs.spring.boot.starter.security) implementation(libs.spring.security.core) + implementation(libs.spring.security.config) + implementation(libs.spring.security.web) + // This dependency contains `ConditionalOnCloudPlatform` + implementation(libs.spring.boot.autoconfigure) + implementation(libs.fabric8.kubernetes.client) testImplementation(libs.spring.security.test) testImplementation(libs.junit.jupiter.api) } diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt similarity index 95% rename from save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt rename to authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt index e9dddc0e62..368a810bbe 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/configs/WebClientCustomizers.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt @@ -2,9 +2,9 @@ * Customization for spring `WebClient` */ -package com.saveourtool.save.configs +package com.saveourtool.save.authservice.config -import com.saveourtool.save.spring.security.SA_HEADER_NAME +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 @@ -28,7 +28,7 @@ import kotlin.io.path.readText * A configuration class that can be used to import all related [WebClientCustomizer] beans. */ @Configuration -open class WebClientCustomizers { +open class SecurityWebClientCustomizers { @Bean @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_CLASS_ELEMENTS") diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt similarity index 98% rename from save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt rename to authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt index 69d3196695..802a7a5bb2 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/spring/security/KubernetesAuthenticationUtils.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt @@ -2,7 +2,7 @@ * Utilities to configure Kubernetes ServiceAccount token-based authentication in Spring Security. */ -package com.saveourtool.save.spring.security +package com.saveourtool.save.authservice.utils import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.getLogger @@ -51,7 +51,7 @@ const val SA_HEADER_NAME = "X-Service-Account-Token" @Suppress( "AVOID_USING_UTILITY_CLASS", // Spring beans need to be declared inside `@Configuration` class. ) -open class KubernetesAuthenticationUtils { +class KubernetesAuthenticationUtils { @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @Bean @Order(2) @@ -59,7 +59,7 @@ open class KubernetesAuthenticationUtils { "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION", ) - open fun internalSecuredSecurityChain( + fun internalSecuredSecurityChain( http: ServerHttpSecurity, serviceAccountAuthenticatingManager: ServiceAccountAuthenticatingManager, serviceAccountTokenExtractorConverter: ServiceAccountTokenExtractorConverter, @@ -102,7 +102,7 @@ open class KubernetesAuthenticationUtils { "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG", ) - open fun internalInsecureSecurityChain( + fun internalInsecureSecurityChain( http: ServerHttpSecurity ): SecurityWebFilterChain = http.run { securityMatcher( diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt index 17109f08c2..de40509d8a 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt @@ -1,6 +1,7 @@ package com.saveourtool.save.backend import com.saveourtool.save.backend.configs.ConfigProperties +import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -21,7 +22,7 @@ typealias StringList = List */ @SpringBootApplication @EnableConfigurationProperties(ConfigProperties::class) -@Import(WebClientCustomizers::class) +@Import(SecurityWebClientCustomizers::class) class SaveApplication fun main(args: Array) { diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt index 67d20e7109..fcd6663d9f 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/configs/WebSecurityConfig.kt @@ -10,7 +10,7 @@ import com.saveourtool.save.authservice.repository.AuthenticationUserRepository import com.saveourtool.save.authservice.security.ConvertingAuthenticationManager import com.saveourtool.save.authservice.security.CustomAuthenticationBasicConverter import com.saveourtool.save.authservice.service.AuthenticationUserDetailsService -import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils +import com.saveourtool.save.authservice.utils.KubernetesAuthenticationUtils import org.springframework.context.annotation.Import import org.springframework.context.annotation.Profile diff --git a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml index 7a8ca68a97..1ad51b464a 100644 --- a/save-cloud-charts/save-cloud/templates/backend-configmap.yaml +++ b/save-cloud-charts/save-cloud/templates/backend-configmap.yaml @@ -12,4 +12,4 @@ data: management.server.port={{ .Values.backend.managementPort }} spring.datasource.url=${spring.datasource.backend-url} logging.level.org.springframework.cloud=DEBUG - logging.level.com.saveourtool.save.spring.security=DEBUG + logging.level.com.saveourtool.save.authservice=DEBUG diff --git a/save-cloud-common/build.gradle.kts b/save-cloud-common/build.gradle.kts index 5bf5aac9d1..6a134ee1d8 100644 --- a/save-cloud-common/build.gradle.kts +++ b/save-cloud-common/build.gradle.kts @@ -43,13 +43,9 @@ kotlin { val jvmMain by getting { dependencies { implementation(project.dependencies.platform(libs.spring.boot.dependencies)) - implementation(libs.spring.security.core) - implementation(libs.spring.security.web) - implementation(libs.spring.security.config) implementation(libs.spring.web) implementation(libs.spring.webflux) implementation(libs.spring.boot) - implementation(libs.spring.boot.autoconfigure) implementation(libs.spring.data.jpa) implementation(libs.jackson.module.kotlin) implementation(libs.hibernate.jpa21.api) @@ -58,7 +54,6 @@ kotlin { implementation(libs.commons.compress) implementation(libs.validation.api) implementation(libs.swagger.annotations) - implementation(libs.fabric8.kubernetes.client) } } val jvmTest by getting { diff --git a/save-orchestrator-common/build.gradle.kts b/save-orchestrator-common/build.gradle.kts index fc58b78593..ded76ba623 100644 --- a/save-orchestrator-common/build.gradle.kts +++ b/save-orchestrator-common/build.gradle.kts @@ -29,6 +29,7 @@ tasks.withType { dependencies { api(projects.saveCloudCommon) + api(projects.authenticationService) implementation(libs.dockerJava.core) implementation(libs.dockerJava.transport.httpclient5) implementation(libs.kotlinx.serialization.json.jvm) diff --git a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt index 0ca2c6ef6a..536da9994b 100644 --- a/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt +++ b/save-orchestrator-common/src/main/kotlin/com/saveourtool/save/orchestrator/config/KubernetesServiceAccountWebSecurityConfig.kt @@ -1,6 +1,6 @@ package com.saveourtool.save.orchestrator.config -import com.saveourtool.save.spring.security.KubernetesAuthenticationUtils +import com.saveourtool.save.authservice.utils.KubernetesAuthenticationUtils import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform import org.springframework.boot.cloud.CloudPlatform import org.springframework.context.annotation.Import diff --git a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt index 54421424e6..8983807c44 100644 --- a/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt +++ b/save-orchestrator/src/main/kotlin/com/saveourtool/save/orchestrator/SaveOrchestrator.kt @@ -1,6 +1,6 @@ package com.saveourtool.save.orchestrator -import com.saveourtool.save.configs.WebClientCustomizers +import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Import @@ -9,7 +9,7 @@ import org.springframework.context.annotation.Import * An entrypoint for spring boot for save-orchestrator */ @SpringBootApplication -@Import(WebClientCustomizers::class) +@Import(SecurityWebClientCustomizers::class) class SaveOrchestrator fun main(args: Array) { diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index 6494ba188b..7a2e853af4 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -1,6 +1,6 @@ package com.saveourtool.save.preprocessor -import com.saveourtool.save.configs.WebClientCustomizers +import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import com.saveourtool.save.preprocessor.config.ConfigProperties import org.springframework.boot.SpringApplication import org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration @@ -28,7 +28,7 @@ typealias StatusResponse = ResponseEntity ]) @EnableWebFlux @EnableConfigurationProperties(ConfigProperties::class) -@Import(WebClientCustomizers::class) +@Import(SecurityWebClientCustomizers::class) class SavePreprocessor fun main(args: Array) { diff --git a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt index 4a15c0376c..89a017899d 100644 --- a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt +++ b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt @@ -1,6 +1,6 @@ package com.saveourtool.save.sandbox -import com.saveourtool.save.configs.WebClientCustomizers +import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Import @@ -9,7 +9,7 @@ import org.springframework.context.annotation.Import * An entrypoint for spring boot for save-sandbox */ @SpringBootApplication -@Import(WebClientCustomizers::class) +@Import(SecurityWebClientCustomizers::class) class SaveSandbox fun main(args: Array) { From ff458a2998a451f2661c8cd35f00744ac4db0d06 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 23 Nov 2022 14:09:28 +0300 Subject: [PATCH 41/57] Code style, minor fixes --- authentication-service/build.gradle.kts | 5 +++-- .../kotlin/com/saveourtool/save/backend/SaveApplication.kt | 1 + save-preprocessor/build.gradle.kts | 1 + .../com/saveourtool/save/preprocessor/SavePreprocessor.kt | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/authentication-service/build.gradle.kts b/authentication-service/build.gradle.kts index 6a2a281547..08ebf2a630 100644 --- a/authentication-service/build.gradle.kts +++ b/authentication-service/build.gradle.kts @@ -27,8 +27,9 @@ dependencies { implementation(libs.spring.security.core) implementation(libs.spring.security.config) implementation(libs.spring.security.web) - // This dependency contains `ConditionalOnCloudPlatform` - implementation(libs.spring.boot.autoconfigure) + 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) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt index de40509d8a..c2b133d3a9 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt @@ -2,6 +2,7 @@ package com.saveourtool.save.backend import com.saveourtool.save.backend.configs.ConfigProperties import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers + import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties diff --git a/save-preprocessor/build.gradle.kts b/save-preprocessor/build.gradle.kts index 95c38ca4ea..49d74a4993 100644 --- a/save-preprocessor/build.gradle.kts +++ b/save-preprocessor/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { implementation(projects.saveCloudCommon) + implementation(projects.authenticationService) testImplementation(projects.testUtils) implementation(libs.save.common.jvm) implementation(libs.save.core.jvm) diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index 7a2e853af4..cdcc04e6f9 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -7,7 +7,6 @@ import org.springframework.boot.actuate.autoconfigure.security.reactive.Reactive import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Import import org.springframework.http.HttpStatus From b5e581ca97b564cefa4b2560d838b18e4d479285 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Wed, 23 Nov 2022 14:30:09 +0300 Subject: [PATCH 42/57] Code style --- .../main/kotlin/com/saveourtool/save/backend/SaveApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt index c2b133d3a9..338d68e941 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt @@ -1,7 +1,7 @@ package com.saveourtool.save.backend -import com.saveourtool.save.backend.configs.ConfigProperties import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers +import com.saveourtool.save.backend.configs.ConfigProperties import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication From 6a3584ca85a08e310ac656d39723ad41c90af79d Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 09:45:48 +0300 Subject: [PATCH 43/57] Try to build w/o caches --- .github/workflows/build_and_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 69231d0847..8a8820f334 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -58,6 +58,8 @@ jobs: with: gradle-version: wrapper gradle-home-cache-cleanup: true + # TODO: remove + cache-disabled: true arguments: | build -x detekt From e3b9d9cada6e0035c52ddc3f3fb5d8f4d086f2a7 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 09:59:37 +0300 Subject: [PATCH 44/57] One more experiment to fix Kapt error --- .github/workflows/build_and_test.yml | 2 -- .../save/buildutils/spring-boot-configuration.gradle.kts | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8a8820f334..69231d0847 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -58,8 +58,6 @@ jobs: with: gradle-version: wrapper gradle-home-cache-cleanup: true - # TODO: remove - cache-disabled: true arguments: | build -x detekt diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts index 4ee1d21b12..86f10d0d51 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts @@ -18,6 +18,10 @@ extensions.getByType().jvmToolchain { languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } +kapt { + correctErrorTypes = true +} + @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") val libs = the() dependencies { From 2afa56429406636efab7df446309c85330d236dd Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 10:08:58 +0300 Subject: [PATCH 45/57] One more experiment to fix Kapt error --- .../save/buildutils/spring-boot-configuration.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts index 86f10d0d51..c47bb55b03 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts @@ -19,7 +19,8 @@ extensions.getByType().jvmToolchain { } kapt { - correctErrorTypes = true +// correctErrorTypes = true + useBuildCache = false } @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") From aa06a2cffc125ab0c9193cf72dc3a5cd242186fd Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 10:15:39 +0300 Subject: [PATCH 46/57] One more experiment to fix Kapt error --- .../save/buildutils/spring-boot-configuration.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts index c47bb55b03..25245f087b 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts @@ -19,7 +19,7 @@ extensions.getByType().jvmToolchain { } kapt { -// correctErrorTypes = true + correctErrorTypes = true useBuildCache = false } From 4ae4edd49cf11b4bcd8bfc05429c452350e9a142 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 10:22:46 +0300 Subject: [PATCH 47/57] One more experiment to fix Kapt error --- .../main/kotlin/com/saveourtool/save/backend/SaveApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt index 338d68e941..6fb325767e 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/SaveApplication.kt @@ -23,7 +23,7 @@ typealias StringList = List */ @SpringBootApplication @EnableConfigurationProperties(ConfigProperties::class) -@Import(SecurityWebClientCustomizers::class) +@org.springframework.context.annotation.Import(com.saveourtool.save.authservice.config.SecurityWebClientCustomizers::class) class SaveApplication fun main(args: Array) { From ac5755672dfd94e5475e9dd9e28abb52565e3f96 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 24 Nov 2022 12:57:41 +0300 Subject: [PATCH 48/57] Remove most of JPA related depdencies from auth service; revert change in kapt config --- authentication-service/build.gradle.kts | 2 +- .../save/buildutils/spring-boot-configuration.gradle.kts | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/authentication-service/build.gradle.kts b/authentication-service/build.gradle.kts index 08ebf2a630..ef279173e2 100644 --- a/authentication-service/build.gradle.kts +++ b/authentication-service/build.gradle.kts @@ -3,7 +3,6 @@ import com.saveourtool.save.buildutils.* plugins { id("com.saveourtool.save.buildutils.kotlin-jvm-configuration") id("com.saveourtool.save.buildutils.spring-boot-configuration") - id("com.saveourtool.save.buildutils.spring-data-configuration") kotlin("plugin.allopen") alias(libs.plugins.kotlin.plugin.jpa) } @@ -24,6 +23,7 @@ kotlin { dependencies { implementation(projects.saveCloudCommon) implementation(libs.spring.boot.starter.security) + implementation("org.springframework:spring-jdbc") implementation(libs.spring.security.core) implementation(libs.spring.security.config) implementation(libs.spring.security.web) diff --git a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts index 25245f087b..4ee1d21b12 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/save/buildutils/spring-boot-configuration.gradle.kts @@ -18,11 +18,6 @@ extensions.getByType().jvmToolchain { languageVersion.set(JavaLanguageVersion.of(Versions.jdk)) } -kapt { - correctErrorTypes = true - useBuildCache = false -} - @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") val libs = the() dependencies { From 804dffa7c9ae4aba1233079702a4c87e53ee9dfa Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 8 Dec 2022 09:32:44 +0300 Subject: [PATCH 49/57] Follow-ups after merge --- authentication-service/build.gradle.kts | 3 +-- .../config/SecurityWebClientCustomizers.kt | 20 +++++++++++++------ gradle/libs.versions.toml | 1 + .../save/preprocessor/SavePreprocessor.kt | 11 +--------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/authentication-service/build.gradle.kts b/authentication-service/build.gradle.kts index 13892cacc3..8527e43799 100644 --- a/authentication-service/build.gradle.kts +++ b/authentication-service/build.gradle.kts @@ -23,9 +23,8 @@ kotlin { dependencies { implementation(projects.saveCloudCommon) implementation(libs.spring.boot.starter.security) - implementation("org.springframework:spring-jdbc") + implementation(libs.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) { diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt index 368a810bbe..0b1104bbc2 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/SecurityWebClientCustomizers.kt @@ -28,13 +28,20 @@ import kotlin.io.path.readText * A configuration class that can be used to import all related [WebClientCustomizer] beans. */ @Configuration -open class SecurityWebClientCustomizers { +class SecurityWebClientCustomizers { @Bean @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) + @Suppress("") + /** + * @param expirationTimeMinutes + * Service account token will be cached in memory for this number of minutes and re-read after it passes. + * See also [docs](https://kubernetes.io/docs/concepts/storage/projected-volumes/#serviceaccounttoken). + * @param tokenPath mount path for SA token as specified in Pod spec + */ + fun serviceAccountTokenHeaderWebClientCustomizer( + @Value("\${com.saveourtool.cloud.kubernetes.sa-token.expiration.minutes:5}") expirationTimeMinutes: Long, + @Value("\${com.saveourtool.cloud.kubernetes.sa-token.path:/var/run/secrets/tokens/service-account-projected-token}") tokenPath: String, + ) = ServiceAccountTokenHeaderWebClientCustomizer(expirationTimeMinutes, tokenPath) } /** @@ -44,11 +51,12 @@ open class SecurityWebClientCustomizers { */ class ServiceAccountTokenHeaderWebClientCustomizer( expirationTimeMinutes: Long, + tokenPath: String, ) : WebClientCustomizer { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() private val wrapper = ExpiringValueWrapper(Duration.ofMinutes(expirationTimeMinutes)) { - val token = Path.of("/var/run/secrets/tokens/service-account-projected-token").readText() + val token = Path.of(tokenPath).readText() token } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c68be8b46b..b82d1d1640 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -107,6 +107,7 @@ spring-kafka = { module = "org.springframework.kafka:spring-kafka" } spring-kafka-test = { module = "org.springframework.kafka:spring-kafka-test" } spring-web = { module = "org.springframework:spring-web" } spring-webflux = { module = "org.springframework:spring-webflux" } +spring-jdbc = { module = "org.springframework:spring-jdbc" } spring-jdbc-starter = { module = "org.springframework.boot:spring-boot-starter-data-jdbc" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin" } diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index 077c05eb06..b32542c93e 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -3,24 +3,15 @@ package com.saveourtool.save.preprocessor import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import com.saveourtool.save.preprocessor.config.ConfigProperties import org.springframework.boot.SpringApplication -import org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Import -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity import org.springframework.web.reactive.config.EnableWebFlux /** * An entrypoint for spring for save-preprocessor */ -@SpringBootApplication(exclude = [ - ReactiveSecurityAutoConfiguration::class, - ReactiveUserDetailsServiceAutoConfiguration::class, - ReactiveManagementWebSecurityAutoConfiguration::class, -]) +@SpringBootApplication @EnableWebFlux @EnableConfigurationProperties(ConfigProperties::class) @Import(SecurityWebClientCustomizers::class) From dce76ae5628f90e0cf4e2e93c9cee85c46dc9616 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 8 Dec 2022 10:32:31 +0300 Subject: [PATCH 50/57] Update helm_push.yml --- .github/workflows/helm_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/helm_push.yml b/.github/workflows/helm_push.yml index 301515252e..e4c4db648f 100644 --- a/.github/workflows/helm_push.yml +++ b/.github/workflows/helm_push.yml @@ -41,7 +41,7 @@ jobs: - if: github.event_name == 'workflow_dispatch' name: Prepare to build from branch run: | - git switch -c origin/${{ inputs.branch }} + git switch -c ${{ inputs.branch }} origin/${{ inputs.branch }} - uses: gittools/actions/gitversion/setup@v0.9.15 with: versionSpec: 5.x From 467483cb254260a298b971c7f9a6d92ebd735a93 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 8 Dec 2022 10:34:33 +0300 Subject: [PATCH 51/57] Update helm_push.yml --- .github/workflows/helm_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/helm_push.yml b/.github/workflows/helm_push.yml index e4c4db648f..d6d366cb02 100644 --- a/.github/workflows/helm_push.yml +++ b/.github/workflows/helm_push.yml @@ -41,7 +41,7 @@ jobs: - if: github.event_name == 'workflow_dispatch' name: Prepare to build from branch run: | - git switch -c ${{ inputs.branch }} origin/${{ inputs.branch }} + git switch --force-create ${{ inputs.branch }} origin/${{ inputs.branch }} - uses: gittools/actions/gitversion/setup@v0.9.15 with: versionSpec: 5.x From 64bd7df436432da94bb26f52fe670e844f04f7e3 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 12 Dec 2022 15:44:58 +0300 Subject: [PATCH 52/57] Fixme until #1247 --- .../authservice/utils/KubernetesAuthenticationUtils.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt index 802a7a5bb2..3623a462b2 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt @@ -74,6 +74,15 @@ class KubernetesAuthenticationUtils { // all requests to `/actuator` should be sent only from inside the cluster // access to this port should be controlled by a NetworkPolicy .permitAll() + .pathMatchers( + // FixMe: https://github.com/saveourtool/save-cloud/pull/1247 + "/internal/files/download-save-agent", + "/internal/files/download-save-cli", + "/internal/files/download", + "/internal/test-suites-sources/download-snapshot-by-execution-id", + "/heartbeat", + ) + .permitAll() .and() .authorizeExchange() .pathMatchers("/**") From bbd1b34dcec86910eabbf9a0e85835f66408fe7b Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 12 Dec 2022 15:45:41 +0300 Subject: [PATCH 53/57] Eclude security autoconfuguration from preprocessor --- .../saveourtool/save/preprocessor/SavePreprocessor.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt index b32542c93e..beeda260b2 100644 --- a/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt +++ b/save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/SavePreprocessor.kt @@ -2,8 +2,12 @@ package com.saveourtool.save.preprocessor import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers import com.saveourtool.save.preprocessor.config.ConfigProperties + import org.springframework.boot.SpringApplication +import org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Import import org.springframework.web.reactive.config.EnableWebFlux @@ -11,7 +15,11 @@ import org.springframework.web.reactive.config.EnableWebFlux /** * An entrypoint for spring for save-preprocessor */ -@SpringBootApplication +@SpringBootApplication(exclude = [ + ReactiveSecurityAutoConfiguration::class, + ReactiveUserDetailsServiceAutoConfiguration::class, + ReactiveManagementWebSecurityAutoConfiguration::class, +]) @EnableWebFlux @EnableConfigurationProperties(ConfigProperties::class) @Import(SecurityWebClientCustomizers::class) From df910bb74c7080fdf24cf53dc86bad066336ed96 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 15 Dec 2022 07:34:25 +0300 Subject: [PATCH 54/57] Add more endpoints to exclusion list --- .../save/authservice/utils/KubernetesAuthenticationUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt index 3623a462b2..53acb4a730 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt @@ -79,7 +79,9 @@ class KubernetesAuthenticationUtils { "/internal/files/download-save-agent", "/internal/files/download-save-cli", "/internal/files/download", + "/internal/files/debug-info", "/internal/test-suites-sources/download-snapshot-by-execution-id", + "/internal/saveTestResult", "/heartbeat", ) .permitAll() From ee6e5a1859579f1b926fda4ba4be89e5c485d6f6 Mon Sep 17 00:00:00 2001 From: sanyavertolet Date: Wed, 5 Apr 2023 12:24:25 +0300 Subject: [PATCH 55/57] [skip ci] updated branch --- gradle/libs.versions.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ba3d789ad..168a13ed19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -191,7 +191,6 @@ detekt-gradle-plugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plu reckon-gradle-plugin = { module = "org.ajoberstar.reckon:reckon-gradle", version.ref = "reckon" } commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commons-compress" } commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } -picocli = { module = "info.picocli:picocli", version.ref = "picocli" } zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } kotlinx-cli = { module = "org.jetbrains.kotlinx:kotlinx-cli", version.ref = "kotlinx-cli" } gradle-plugin-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } From 5c3c6814b612cdfe1f4cc9713f15e60e5cf9e84a Mon Sep 17 00:00:00 2001 From: sanyavertolet Date: Wed, 5 Apr 2023 14:45:14 +0300 Subject: [PATCH 56/57] [skip ci] added SA to demo and gateway --- api-gateway/build.gradle.kts | 2 +- .../service/AuthenticationUserDetailsService.kt | 5 +++-- .../utils/KubernetesAuthenticationUtils.kt | 16 ++++++++-------- .../save-cloud/templates/demo-deployment.yaml | 2 ++ .../templates/demo-service-account.yaml | 16 ++++++++++++++++ .../save-cloud/templates/gateway-deployment.yaml | 3 +++ ...ts.yaml => microservice-service-account.yaml} | 0 save-demo/build.gradle.kts | 2 +- save-orchestrator/build.gradle.kts | 2 +- save-sandbox/build.gradle.kts | 2 +- .../com/saveourtool/save/sandbox/SaveSandbox.kt | 2 +- 11 files changed, 37 insertions(+), 15 deletions(-) rename save-cloud-charts/save-cloud/templates/{service-accounts.yaml => microservice-service-account.yaml} (100%) diff --git a/api-gateway/build.gradle.kts b/api-gateway/build.gradle.kts index cc4faf9b91..7b491533ad 100644 --- a/api-gateway/build.gradle.kts +++ b/api-gateway/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { implementation(libs.spring.cloud.starter.gateway) implementation(libs.spring.boot.starter.security) implementation(libs.spring.boot.starter.oauth2.client) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) implementation(libs.spring.security.core) implementation(projects.authenticationService) } diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/service/AuthenticationUserDetailsService.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/service/AuthenticationUserDetailsService.kt index c710b6f5af..e1f5689b42 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/service/AuthenticationUserDetailsService.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/service/AuthenticationUserDetailsService.kt @@ -2,6 +2,7 @@ package com.saveourtool.save.authservice.service import com.saveourtool.save.authservice.repository.AuthenticationUserRepository import com.saveourtool.save.authservice.utils.getIdentitySourceAwareUserDetails +import com.saveourtool.save.utils.blockingToMono import org.springframework.context.annotation.Primary import org.springframework.security.core.userdetails.ReactiveUserDetailsService import org.springframework.security.core.userdetails.UserDetails @@ -21,7 +22,7 @@ class AuthenticationUserDetailsService( * @param username * @return IdentitySourceAwareUserDetails retrieved from UserDetails */ - override fun findByUsername(username: String): Mono = { + override fun findByUsername(username: String): Mono = blockingToMono { authenticationUserRepository.findByName(username) - }.toMono().getIdentitySourceAwareUserDetails(username) + }.getIdentitySourceAwareUserDetails(username) } diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt index 53acb4a730..78af629508 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/KubernetesAuthenticationUtils.kt @@ -188,14 +188,14 @@ class ServiceAccountAuthenticatingManager( @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() + |apiVersion: authentication.k8s.io/v1 + |kind: TokenReview + |metadata: + | name: service-account-validity-check + | namespace: ${kubernetesClient.namespace} + |spec: + | token: $token + """.trimMargin() } /** diff --git a/save-cloud-charts/save-cloud/templates/demo-deployment.yaml b/save-cloud-charts/save-cloud/templates/demo-deployment.yaml index d17c1322f0..69a94555f2 100644 --- a/save-cloud-charts/save-cloud/templates/demo-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/demo-deployment.yaml @@ -57,6 +57,7 @@ spec: mountPath: {{ .Values.mysql.dbPasswordFile }} - name: s3-secrets mountPath: {{ .Values.s3.secretFile }} + - {{ include "spring-boot.sa-token-mount" . | indent 14 | trim }} {{- include "spring-boot.management" .Values.demo | nindent 10 }} resources: limits: @@ -130,3 +131,4 @@ spec: secretName: s3-secrets - name: migrations-data emptyDir: { } + - {{ include "spring-boot.sa-token-volume" (dict "service" .Values.demo) | indent 10 | trim }} diff --git a/save-cloud-charts/save-cloud/templates/demo-service-account.yaml b/save-cloud-charts/save-cloud/templates/demo-service-account.yaml index 5a19e70521..62017ffd64 100644 --- a/save-cloud-charts/save-cloud/templates/demo-service-account.yaml +++ b/save-cloud-charts/save-cloud/templates/demo-service-account.yaml @@ -39,3 +39,19 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: jobs-demo-executor + +--- + +# Bind orchestrator-sa to ClusterRole required for TokenReview access +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: demo-microservice-role-binding +subjects: + - kind: ServiceAccount + name: demo-sa + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: microservice diff --git a/save-cloud-charts/save-cloud/templates/gateway-deployment.yaml b/save-cloud-charts/save-cloud/templates/gateway-deployment.yaml index 2c21db2c56..ec935b6134 100644 --- a/save-cloud-charts/save-cloud/templates/gateway-deployment.yaml +++ b/save-cloud-charts/save-cloud/templates/gateway-deployment.yaml @@ -16,6 +16,7 @@ spec: annotations: {{- include "pod.common.annotations" (dict "service" .Values.backend ) | nindent 8 }} spec: + serviceAccountName: microservice-sa restartPolicy: Always {{- include "cnb.securityContext" . | nindent 6 }} containers: @@ -27,6 +28,7 @@ spec: value: /home/cnb/secrets/oauth - name: JAVA_TOOL_OPTIONS value: -XX:ReservedCodeCacheSize=48M -Dreactor.netty.http.server.accessLogEnabled=true + - {{ include "spring-boot.sa-token-mount" . | indent 14 | trim }} {{- include "spring-boot.management" .Values.gateway | nindent 10 }} resources: limits: @@ -42,3 +44,4 @@ spec: - name: oauth-credentials secret: secretName: oauth-credentials + - {{ include "spring-boot.sa-token-volume" (dict "service" .Values.gateway) | indent 10 | trim }} diff --git a/save-cloud-charts/save-cloud/templates/service-accounts.yaml b/save-cloud-charts/save-cloud/templates/microservice-service-account.yaml similarity index 100% rename from save-cloud-charts/save-cloud/templates/service-accounts.yaml rename to save-cloud-charts/save-cloud/templates/microservice-service-account.yaml diff --git a/save-demo/build.gradle.kts b/save-demo/build.gradle.kts index da9ba6a007..8602b92c94 100644 --- a/save-demo/build.gradle.kts +++ b/save-demo/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { api(projects.saveCloudCommon) implementation(libs.save.common.jvm) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) api(libs.ktor.client.auth) implementation(libs.ktor.client.core) diff --git a/save-orchestrator/build.gradle.kts b/save-orchestrator/build.gradle.kts index 8ed0c9aeb6..006cd833dd 100644 --- a/save-orchestrator/build.gradle.kts +++ b/save-orchestrator/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { implementation(libs.commons.compress) implementation(libs.kotlinx.datetime) implementation(libs.zip4j) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) implementation(libs.fabric8.kubernetes.client) implementation(libs.spring.kafka) testImplementation(projects.testUtils) diff --git a/save-sandbox/build.gradle.kts b/save-sandbox/build.gradle.kts index 4a035669c3..53bce160db 100644 --- a/save-sandbox/build.gradle.kts +++ b/save-sandbox/build.gradle.kts @@ -27,7 +27,7 @@ tasks.withType { dependencies { implementation(projects.saveOrchestratorCommon) implementation(libs.zip4j) - implementation(libs.spring.cloud.starter.kubernetes.client.config) + implementation(libs.spring.cloud.starter.kubernetes.fabric8.config) implementation(libs.hibernate.jpa21.api) implementation(libs.save.plugins.warn.jvm) implementation(projects.authenticationService) diff --git a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt index 0f79e3f901..493fc033f6 100644 --- a/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt +++ b/save-sandbox/src/main/kotlin/com/saveourtool/save/sandbox/SaveSandbox.kt @@ -1,8 +1,8 @@ package com.saveourtool.save.sandbox import com.saveourtool.save.authservice.config.SecurityWebClientCustomizers +import com.saveourtool.save.orchestrator.config.ConfigProperties import com.saveourtool.save.s3.DefaultS3Configuration -import com.saveourtool.save.sandbox.config.ConfigProperties import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.domain.EntityScan From 925d062ed3fe44f8b6ada4458f41663bd8fc29a3 Mon Sep 17 00:00:00 2001 From: sanyavertolet Date: Wed, 5 Apr 2023 15:21:02 +0300 Subject: [PATCH 57/57] [skip ci] wrapper tokenewviews --- .../save-cloud/templates/microservice-service-account.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save-cloud-charts/save-cloud/templates/microservice-service-account.yaml b/save-cloud-charts/save-cloud/templates/microservice-service-account.yaml index 9ed0673707..f33bd3fe46 100644 --- a/save-cloud-charts/save-cloud/templates/microservice-service-account.yaml +++ b/save-cloud-charts/save-cloud/templates/microservice-service-account.yaml @@ -38,7 +38,7 @@ metadata: name: microservice rules: - apiGroups: ["authentication.k8s.io"] - resources: [tokenreviews] + resources: ["tokenreviews"] verbs: ["create"] ---