Skip to content

Commit 58d6a60

Browse files
malaw-mojEwan Donovan
and
Ewan Donovan
authored
Ssl only made once (#72)
* First fixes * refactor(*): add cert generation script for the tests and lrs-api-client bean for singleton --------- Co-authored-by: Ewan Donovan <ewan.donovan@digital.justice.com>
1 parent c4f7886 commit 58d6a60

File tree

10 files changed

+67
-56
lines changed

10 files changed

+67
-56
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ sonar-project.properties
7777

7878
# Certificate files
7979
*.pfx
80+
*.jks
8081

8182
# env files
8283
.env*

Dockerfile

-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ COPY --from=builder --chown=appuser:appgroup /app/build/libs/applicationinsights
2929
COPY --from=builder --chown=appuser:appgroup /app/applicationinsights.json /app
3030
COPY --from=builder --chown=appuser:appgroup /app/applicationinsights.dev.json /app
3131
#COPY WebServiceClientCert.pfx /app/WebServiceClientCert.pfx
32-
#RUN ls -la /app/WebServiceClientCert.pfx
33-
3432
USER 2000
3533

3634
ENTRYPOINT ["java", "-XX:+AlwaysActAsServerClassMachine", "-javaagent:/app/agent.jar", "-jar", "/app/app.jar"]

build.gradle.kts

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@ kotlin {
3232
jvmToolchain(21)
3333
}
3434

35+
tasks.register<Exec>("generateSSLCertificate") {
36+
commandLine("bash", "./generateSSLCertificate.sh")
37+
}
38+
3539
tasks {
3640
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
3741
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
3842
}
3943
test {
40-
environment("PFX_FILE_PASSWORD", "TEST")
44+
dependsOn("generateSSLCertificate")
45+
environment("PFX_FILE_PASSWORD", "changeit")
4146
environment("UK_PRN", "TEST")
4247
environment("ORG_PASSWORD", "TEST")
4348
environment("VENDOR_ID", "TEST")

generateSSLCertificate.sh

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
# Define variables for file names, passwords, etc.
4+
KEYSTORE_PASSWORD="changeit"
5+
ALIAS="dummyClientAlias"
6+
KEYSTORE_FILE="dummyClient.jks"
7+
PFX_FILE="WebServiceClientCert.pfx"
8+
KEY_PASSWORD="changeit"
9+
VALIDITY_DAYS=3650
10+
11+
rm -rf *.pfx *.jks
12+
13+
# Generate a self-signed certificate using keytool
14+
keytool -genkeypair -keyalg RSA -keysize 2048 -alias $ALIAS -keystore $KEYSTORE_FILE -storepass $KEYSTORE_PASSWORD -keypass $KEY_PASSWORD -validity $VALIDITY_DAYS -dname "CN=Dummy Client, OU=Test, O=Test Corp, L=Test City, S=Test State, C=US" -noprompt
15+
16+
# Convert the generated JKS keystore to PFX format
17+
keytool -importkeystore -srckeystore $KEYSTORE_FILE -srcstorepass $KEYSTORE_PASSWORD -destkeystore $PFX_FILE -deststoretype PKCS12 -deststorepass $KEYSTORE_PASSWORD -noprompt
18+
19+
echo "Self-signed certificate generated and converted to PFX format."

src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/HttpClientConfiguration.kt

+35-39
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,53 @@ import okhttp3.logging.HttpLoggingInterceptor.Level
66
import org.slf4j.LoggerFactory
77
import org.springframework.beans.factory.annotation.Autowired
88
import org.springframework.beans.factory.annotation.Value
9+
import org.springframework.context.annotation.Bean
910
import org.springframework.context.annotation.Configuration
1011
import retrofit2.Retrofit
1112
import retrofit2.converter.jaxb.JaxbConverterFactory
13+
import uk.gov.justice.digital.hmpps.learnerrecordsapi.interfaces.LRSApiInterface
1214
import java.util.concurrent.TimeUnit
1315

1416
@Configuration
15-
class HttpClientConfiguration(
16-
@Value("\${lrs.pfx-path}") val pfxFilePath: String,
17-
@Value("\${lrs.base-url}") val baseUrl: String,
17+
class HttpClientConfiguration {
18+
19+
@Value("\${lrs.pfx-path}")
20+
lateinit var pfxFilePath: String
21+
22+
@Value("\${lrs.base-url}")
23+
lateinit var baseUrl: String
24+
1825
@Autowired
19-
private val appConfig: AppConfig,
20-
) {
21-
fun buildSSLHttpClient(): OkHttpClient {
26+
lateinit var appConfig: AppConfig
27+
28+
@Bean
29+
fun lrsClient(): LRSApiInterface = Retrofit.Builder()
30+
.baseUrl(baseUrl)
31+
.client(sslHttpClient())
32+
.addConverterFactory(JaxbConverterFactory.create())
33+
.build().create(LRSApiInterface::class.java)
34+
35+
companion object {
36+
private val log = LoggerFactory.getLogger(this::class.java)
37+
}
38+
39+
fun sslHttpClient(): OkHttpClient {
2240
log.info("Building HTTP client with SSL")
2341
val loggingInterceptor = HttpLoggingInterceptor()
2442
loggingInterceptor.level = Level.BODY
2543

26-
try {
27-
val sslContextConfiguration = SSLContextConfiguration(pfxFilePath)
28-
val sslContext = sslContextConfiguration.createSSLContext()
29-
val trustManager = sslContextConfiguration.getTrustManager()
30-
31-
val httpClientBuilder = OkHttpClient.Builder()
32-
.connectTimeout(appConfig.lrsConnectTimeout(), TimeUnit.SECONDS)
33-
.writeTimeout(appConfig.lrsWriteTimeout(), TimeUnit.SECONDS)
34-
.readTimeout(appConfig.lrsReadTimeout(), TimeUnit.SECONDS)
35-
.sslSocketFactory(sslContext.socketFactory, trustManager)
36-
.addInterceptor(loggingInterceptor)
37-
38-
log.info("HTTP client with SSL built successfully!")
39-
return httpClientBuilder.build()
40-
} catch (e: Exception) {
41-
log.info(e.message + " Falling back to HTTP client without SSL")
42-
val httpClientBuilder = OkHttpClient.Builder()
43-
.addInterceptor(loggingInterceptor)
44-
45-
return httpClientBuilder.build()
46-
}
47-
}
44+
val sslContextConfiguration = SSLContextConfiguration(pfxFilePath)
45+
val sslContext = sslContextConfiguration.createSSLContext()
46+
val trustManager = sslContextConfiguration.getTrustManager()
4847

49-
fun retrofit(): Retrofit {
50-
log.info("Retrofit Client")
48+
val httpClientBuilder = OkHttpClient.Builder()
49+
.connectTimeout(appConfig.lrsConnectTimeout(), TimeUnit.SECONDS)
50+
.writeTimeout(appConfig.lrsWriteTimeout(), TimeUnit.SECONDS)
51+
.readTimeout(appConfig.lrsReadTimeout(), TimeUnit.SECONDS)
52+
.sslSocketFactory(sslContext.socketFactory, trustManager)
53+
.addInterceptor(loggingInterceptor)
5154

52-
return Retrofit.Builder()
53-
.baseUrl(baseUrl)
54-
.client(buildSSLHttpClient())
55-
.addConverterFactory(JaxbConverterFactory.create())
56-
.build()
57-
}
58-
59-
companion object {
60-
private val log = LoggerFactory.getLogger(this::class.java)
55+
log.info("HTTP client with SSL built successfully!")
56+
return httpClientBuilder.build()
6157
}
6258
}

src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsService.kt

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired
44
import org.springframework.stereotype.Service
55
import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.AppConfig
66
import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.HttpClientConfiguration
7-
import uk.gov.justice.digital.hmpps.learnerrecordsapi.interfaces.LRSApiInterface
87
import uk.gov.justice.digital.hmpps.learnerrecordsapi.logging.LoggerUtil
98
import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.LearningEventsResponse
109
import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.MIAPAPIException
@@ -24,8 +23,6 @@ class LearnerEventsService(
2423
) {
2524
private val log: LoggerUtil = LoggerUtil(javaClass)
2625

27-
private fun lrsClient(): LRSApiInterface = httpClientConfiguration.retrofit().create(LRSApiInterface::class.java)
28-
2926
private fun parseError(xmlString: String): MIAPAPIException? {
3027
val regex = Regex("<ns10:MIAPAPIException[\\s\\S]*?</ns10:MIAPAPIException>")
3128
val match = regex.find(xmlString)
@@ -41,7 +38,7 @@ class LearnerEventsService(
4138
.transformToLRSRequest(appConfig.ukprn(), appConfig.password(), appConfig.vendorId(), userName)
4239
log.debug("Calling LRS API")
4340

44-
val learningEventsResponse = lrsClient().getLearnerLearningEvents(requestBody)
41+
val learningEventsResponse = httpClientConfiguration.lrsClient().getLearnerLearningEvents(requestBody)
4542
val learningEventsObject = learningEventsResponse.body()?.body?.learningEventsResponse
4643

4744
if (learningEventsResponse.isSuccessful && learningEventsObject != null) {

src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnersService.kt

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired
44
import org.springframework.stereotype.Service
55
import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.AppConfig
66
import uk.gov.justice.digital.hmpps.learnerrecordsapi.config.HttpClientConfiguration
7-
import uk.gov.justice.digital.hmpps.learnerrecordsapi.interfaces.LRSApiInterface
87
import uk.gov.justice.digital.hmpps.learnerrecordsapi.logging.LoggerUtil
98
import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.FindLearnerResponse
109
import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.response.Learner
@@ -27,8 +26,6 @@ class LearnersService(
2726

2827
private val log: LoggerUtil = LoggerUtil(javaClass)
2928

30-
private fun lrsClient(): LRSApiInterface = httpClientConfiguration.retrofit().create(LRSApiInterface::class.java)
31-
3229
private fun parseError(xmlString: String): MIAPAPIException? {
3330
val regex = Regex("<ns10:MIAPAPIException[\\s\\S]*?</ns10:MIAPAPIException>")
3431
val match = regex.find(xmlString)
@@ -45,7 +42,7 @@ class LearnersService(
4542

4643
log.debug("Calling LRS API")
4744

48-
val lrsResponse = lrsClient().findLearnerByDemographics(requestBody)
45+
val lrsResponse = httpClientConfiguration.lrsClient().findLearnerByDemographics(requestBody)
4946
val lrsResponseBody = lrsResponse.body()?.body?.findLearnerResponse
5047

5148
if (lrsResponse.isSuccessful && lrsResponseBody != null) {

src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/config/TestExceptionResource.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ class TestExceptionResource(
5353
@PostMapping("/test/okhttp-timeout")
5454
fun triggerOkhttpTimeout(): String? {
5555
val mockTimeoutServer = MockWebServer()
56-
mockTimeoutServer.enqueue(MockResponse().setBody("Delayed response").setBodyDelay(15, TimeUnit.SECONDS))
56+
mockTimeoutServer.enqueue(MockResponse().setBody("Delayed response").setBodyDelay(40, TimeUnit.SECONDS))
5757
mockTimeoutServer.start()
5858

5959
val request = Request.Builder()
6060
.url(mockTimeoutServer.url("/timeout")) // Point to the MockWebServer URL
6161
.build()
6262

63-
val response = httpClientConfiguration.buildSSLHttpClient().newCall(request).execute()
63+
val response = httpClientConfiguration.sslHttpClient().newCall(request).execute()
6464
return response.body?.string()
6565
}
6666
}

src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnerEventsServiceTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ class LearnerEventsServiceTest {
4545
retrofitMock = mock(Retrofit::class.java)
4646
lrsApiInterfaceMock =
4747
mock(LRSApiInterface::class.java)
48-
`when`(httpClientConfigurationMock.retrofit()).thenReturn(retrofitMock)
49-
`when`(retrofitMock.create(LRSApiInterface::class.java)).thenReturn(lrsApiInterfaceMock)
48+
`when`(httpClientConfigurationMock.lrsClient()).thenReturn(lrsApiInterfaceMock)
5049
appConfigMock = mock(AppConfig::class.java)
5150
learnerEventsService = LearnerEventsService(httpClientConfigurationMock, appConfigMock)
5251
`when`(appConfigMock.ukprn()).thenReturn("test")

src/test/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/service/LearnersServiceTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ class LearnersServiceTest {
4848
retrofitMock = mock(Retrofit::class.java)
4949
lrsApiInterfaceMock =
5050
mock(LRSApiInterface::class.java)
51-
`when`(httpClientConfigurationMock.retrofit()).thenReturn(retrofitMock)
52-
`when`(retrofitMock.create(LRSApiInterface::class.java)).thenReturn(lrsApiInterfaceMock)
51+
`when`(httpClientConfigurationMock.lrsClient()).thenReturn(lrsApiInterfaceMock)
5352
appConfigMock = mock(AppConfig::class.java)
5453
learnersService = LearnersService(httpClientConfigurationMock, appConfigMock)
5554
`when`(appConfigMock.ukprn()).thenReturn("test")

0 commit comments

Comments
 (0)