diff --git a/build.gradle.kts b/build.gradle.kts index 015845e..3d9e1d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,25 +20,9 @@ repositories { mavenCentral() } -extra["springCloudVersion"] = "2023.0.1" - -dependencyManagement { - imports { - mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}") - } -} - dependencies { implementation("org.springframework.boot:spring-boot-starter") - implementation("org.springframework.cloud:spring-cloud-starter-openfeign") implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("io.jsonwebtoken:jjwt-api:0.11.2") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("org.bouncycastle:bcprov-jdk18on:1.78") - implementation("org.bouncycastle:bcpkix-jdk18on:1.78") - runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2") - runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2") - testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/kotlin/net/leanix/githubagent/GithubAgentApplication.kt b/src/main/kotlin/net/leanix/githubagent/GithubAgentApplication.kt index 54ca4dc..58a5e51 100644 --- a/src/main/kotlin/net/leanix/githubagent/GithubAgentApplication.kt +++ b/src/main/kotlin/net/leanix/githubagent/GithubAgentApplication.kt @@ -1,13 +1,9 @@ package net.leanix.githubagent import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication -import org.springframework.cloud.openfeign.EnableFeignClients @SpringBootApplication -@EnableFeignClients(value = ["net.leanix.githubagent.client"]) -@EnableConfigurationProperties(value = [net.leanix.githubagent.config.GithubEnterpriseProperties::class]) class GithubAgentApplication fun main() { diff --git a/src/main/kotlin/net/leanix/githubagent/client/GithubClient.kt b/src/main/kotlin/net/leanix/githubagent/client/GithubClient.kt deleted file mode 100644 index e32aa3a..0000000 --- a/src/main/kotlin/net/leanix/githubagent/client/GithubClient.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.leanix.githubagent.client - -import org.springframework.cloud.openfeign.FeignClient -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestHeader - -@FeignClient(name = "githubClient", url = "\${github-enterprise.baseUrl}") -interface GithubClient { - - @GetMapping("/api/v3/app") - fun getApp( - @RequestHeader("Authorization") jwt: String, - @RequestHeader("Accept") accept: String = "application/vnd.github.v3+json" - ): String -} diff --git a/src/main/kotlin/net/leanix/githubagent/config/AgentSetupValidation.kt b/src/main/kotlin/net/leanix/githubagent/config/AgentSetupValidation.kt deleted file mode 100644 index f10e5c9..0000000 --- a/src/main/kotlin/net/leanix/githubagent/config/AgentSetupValidation.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.leanix.githubagent.config - -import jakarta.annotation.PostConstruct -import net.leanix.githubagent.exceptions.GithubEnterpriseConfigurationMissingException -import org.springframework.stereotype.Component - -@Component -class AgentSetupValidation( - private val githubEnterpriseProperties: GithubEnterpriseProperties -) { - - @PostConstruct - fun validateConfiguration() { - val missingProperties = mutableListOf() - - if (githubEnterpriseProperties.baseUrl.isBlank()) { - missingProperties.add("GITHUB_ENTERPRISE_BASE_URL") - } - if (githubEnterpriseProperties.githubAppId.isBlank()) { - missingProperties.add("GITHUB_ENTERPRISE_GITHUB_APP_ID") - } - if (githubEnterpriseProperties.pemFile.isBlank()) { - missingProperties.add("GITHUB_ENTERPRISE_PEM_FILE") - } - - if (missingProperties.isNotEmpty()) { - throw GithubEnterpriseConfigurationMissingException(missingProperties.joinToString(", ")) - } - } -} diff --git a/src/main/kotlin/net/leanix/githubagent/config/GithubEnterpriseProperties.kt b/src/main/kotlin/net/leanix/githubagent/config/GithubEnterpriseProperties.kt deleted file mode 100644 index 6dae6e3..0000000 --- a/src/main/kotlin/net/leanix/githubagent/config/GithubEnterpriseProperties.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.leanix.githubagent.config - -import org.springframework.boot.context.properties.ConfigurationProperties - -@ConfigurationProperties(prefix = "github-enterprise") -data class GithubEnterpriseProperties( - val baseUrl: String, - val githubAppId: String, - val pemFile: String, -) diff --git a/src/main/kotlin/net/leanix/githubagent/dto/GithubAppResponse.kt b/src/main/kotlin/net/leanix/githubagent/dto/GithubAppResponse.kt deleted file mode 100644 index b75588f..0000000 --- a/src/main/kotlin/net/leanix/githubagent/dto/GithubAppResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.leanix.githubagent.dto - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.annotation.JsonProperty - -@JsonIgnoreProperties(ignoreUnknown = true) -data class GithubAppResponse( - @JsonProperty("name") val name: String -) diff --git a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt b/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt deleted file mode 100644 index 0313795..0000000 --- a/src/main/kotlin/net/leanix/githubagent/exceptions/Exceptions.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.leanix.githubagent.exceptions - -class GithubEnterpriseConfigurationMissingException(properties: String) : RuntimeException( - "Github Enterprise properties '$properties' are not set" -) -class AuthenticationFailedException(message: String) : RuntimeException(message) -class ConnectingToGithubEnterpriseFailedException(message: String) : RuntimeException(message) diff --git a/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt b/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt deleted file mode 100644 index d6c3a89..0000000 --- a/src/main/kotlin/net/leanix/githubagent/runners/PostStartupRunner.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.leanix.githubagent.runners - -import net.leanix.githubagent.services.GithubAuthenticationService -import org.springframework.boot.ApplicationArguments -import org.springframework.boot.ApplicationRunner -import org.springframework.stereotype.Component - -@Component -class PostStartupRunner(private val githubAuthenticationService: GithubAuthenticationService) : ApplicationRunner { - - override fun run(args: ApplicationArguments?) { - githubAuthenticationService.generateJwtToken() - } -} diff --git a/src/main/kotlin/net/leanix/githubagent/services/CachingService.kt b/src/main/kotlin/net/leanix/githubagent/services/CachingService.kt deleted file mode 100644 index 24f9c3b..0000000 --- a/src/main/kotlin/net/leanix/githubagent/services/CachingService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package net.leanix.githubagent.services - -import jakarta.annotation.PostConstruct -import net.leanix.githubagent.config.GithubEnterpriseProperties -import org.springframework.stereotype.Service - -@Service -class CachingService( - private val githubEnterpriseProperties: GithubEnterpriseProperties -) { - private val cache = HashMap() - - @PostConstruct - private fun init() { - cache["baseUrl"] = githubEnterpriseProperties.baseUrl - cache["githubAppId"] = githubEnterpriseProperties.githubAppId - } - - fun set(key: String, value: String) { - cache[key] = value - } - - fun get(key: String): String? { - return cache[key] - } -} diff --git a/src/main/kotlin/net/leanix/githubagent/services/GithubAuthenticationService.kt b/src/main/kotlin/net/leanix/githubagent/services/GithubAuthenticationService.kt deleted file mode 100644 index 3db7ee8..0000000 --- a/src/main/kotlin/net/leanix/githubagent/services/GithubAuthenticationService.kt +++ /dev/null @@ -1,83 +0,0 @@ -package net.leanix.githubagent.services - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.SignatureAlgorithm -import net.leanix.githubagent.client.GithubClient -import net.leanix.githubagent.config.GithubEnterpriseProperties -import net.leanix.githubagent.dto.GithubAppResponse -import net.leanix.githubagent.exceptions.AuthenticationFailedException -import net.leanix.githubagent.exceptions.ConnectingToGithubEnterpriseFailedException -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.slf4j.LoggerFactory -import org.springframework.core.io.ResourceLoader -import org.springframework.stereotype.Service -import java.io.File -import java.io.IOException -import java.nio.charset.Charset -import java.nio.file.Files -import java.security.KeyFactory -import java.security.PrivateKey -import java.security.Security -import java.security.spec.PKCS8EncodedKeySpec -import java.util.* - -@Service -class GithubAuthenticationService( - private val cachingService: CachingService, - private val githubClient: GithubClient, - private val githubEnterpriseProperties: GithubEnterpriseProperties, - private val resourceLoader: ResourceLoader -) { - - companion object { - private const val JWT_EXPIRATION_DURATION = 600000L - private val logger = LoggerFactory.getLogger(GithubAuthenticationService::class.java) - } - - fun generateJwtToken() { - runCatching { - logger.info("Generating JWT token") - Security.addProvider(BouncyCastleProvider()) - val rsaPrivateKey: String = readPrivateKey(loadPemFile()) - val keySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(rsaPrivateKey)) - val privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec) - createJwtToken(privateKey)?.also { - cachingService.set("jwtToken", it) - verifyJwt(it) - } ?: throw AuthenticationFailedException("Failed to generate a valid JWT token") - } - } - - private fun createJwtToken(privateKey: PrivateKey): String? { - return Jwts.builder() - .setIssuedAt(Date()) - .setExpiration(Date(System.currentTimeMillis() + JWT_EXPIRATION_DURATION)) - .setIssuer(cachingService.get("githubAppId")) - .signWith(privateKey, SignatureAlgorithm.RS256) - .compact() - } - - @Throws(IOException::class) - private fun readPrivateKey(file: File): String { - return String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()) - .replace("-----BEGIN RSA PRIVATE KEY-----", "") - .replace(System.lineSeparator().toRegex(), "") - .replace("-----END RSA PRIVATE KEY-----", "") - } - - private fun verifyJwt(jwt: String) { - runCatching { - val githubApp = jacksonObjectMapper().readValue( - githubClient.getApp("Bearer $jwt"), - GithubAppResponse::class.java - ) - logger.info("Authenticated as GitHub App: ${githubApp.name}") - }.onFailure { - throw ConnectingToGithubEnterpriseFailedException("Failed to verify JWT token") - } - } - - private fun loadPemFile() = - File(resourceLoader.getResource("file:${githubEnterpriseProperties.pemFile}").uri) -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml deleted file mode 100644 index 40240bd..0000000 --- a/src/main/resources/application.yaml +++ /dev/null @@ -1,4 +0,0 @@ -github-enterprise: - baseUrl: ${GITHUB_ENTERPRISE_BASE_URL:} - githubAppId: ${GITHUB_APP_ID:} - pemFile: ${PEM_FILE:} \ No newline at end of file diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml deleted file mode 100644 index 8573e17..0000000 --- a/src/test/resources/application.yaml +++ /dev/null @@ -1,4 +0,0 @@ -github-enterprise: - baseUrl: ${GITHUB_ENTERPRISE_BASE_URL:dummy} - githubAppId: ${GITHUB_APP_ID:dummy} - pemFile: ${PEM_FILE:dummy} \ No newline at end of file