diff --git a/.github/workflows/service-ci.yml b/.github/workflows/service-ci.yml index 2c854f99..b0e33920 100644 --- a/.github/workflows/service-ci.yml +++ b/.github/workflows/service-ci.yml @@ -2,6 +2,8 @@ name: Services CI on: push: + branches: + - main paths: - "services/ehr-repo/**" - "services/mesh-forwarder/**" diff --git a/services/re-registration-service/build.gradle b/services/re-registration-service/build.gradle index 1e9d9de4..5d7f5036 100644 --- a/services/re-registration-service/build.gradle +++ b/services/re-registration-service/build.gradle @@ -56,6 +56,7 @@ dependencies { implementation 'software.amazon.awssdk:cloudwatch' implementation 'software.amazon.awssdk:sqs' implementation 'software.amazon.awssdk:sns' + implementation 'software.amazon.awssdk:ssm' implementation 'software.amazon.awssdk:dynamodb' implementation 'com.google.code.gson:gson:2.13.2' diff --git a/services/re-registration-service/docker-compose.localstack-local.yaml b/services/re-registration-service/docker-compose.localstack-local.yaml index 055caf48..f1730967 100644 --- a/services/re-registration-service/docker-compose.localstack-local.yaml +++ b/services/re-registration-service/docker-compose.localstack-local.yaml @@ -7,7 +7,7 @@ services: network_mode: bridge environment: - DOCKER_HOST=unix:///var/run/docker.sock - - SERVICES=sqs,sns,dynamodb,s3 + - SERVICES=sqs,sns,ssm,dynamodb,s3 - GATEWAY_LISTEN=4566 volumes: - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/services/re-registration-service/docker-compose.yaml b/services/re-registration-service/docker-compose.yaml deleted file mode 100644 index 77393130..00000000 --- a/services/re-registration-service/docker-compose.yaml +++ /dev/null @@ -1,15 +0,0 @@ -services: - default: - links: - - localstack:localstack - localstack: - image: localstack/localstack:4.14.0 - ports: - - "4566:4566" - environment: - - SERVICES=sqs,sns,dynamodb - - DEFAULT_REGION=eu-west-2 - - HOSTNAME_EXTERNAL=localstack - volumes: - - "/var/lib/localstack:/var/lib/localstack" - - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/delete_ehr/DeleteEhrIntegrationTest.java b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/delete_ehr/DeleteEhrIntegrationTest.java index 68982e30..9a7a2190 100644 --- a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/delete_ehr/DeleteEhrIntegrationTest.java +++ b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/delete_ehr/DeleteEhrIntegrationTest.java @@ -32,7 +32,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.delete; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -53,8 +52,8 @@ public class DeleteEhrIntegrationTest { @Autowired ActiveSuspensionsDb activeSuspensionsDb; - @Value("${ehrRepoAuthKey}") - private String authKey; + private static final String EXPECTED_PDS_AUTH_HEADER = "Basic dXNlcm5hbWU6dGVzdA=="; + private static final String EXPECTED_EHR_REPO_AUTH_HEADER = "test"; @Value("${aws.reRegistrationsQueueName}") private String reRegistrationsQueueName; @@ -120,7 +119,7 @@ private void stubResponses() { private void setPds200SuccessState() { stubPdsAdaptor.stubFor(get(urlEqualTo("/suspended-patient-status/" + NHS_NUMBER)) - .withHeader("Authorization", equalTo("Basic cmUtcmVnaXN0cmF0aW9uLXNlcnZpY2U6ZGVmYXVsdA==")) + .withHeader("Authorization", equalTo(EXPECTED_PDS_AUTH_HEADER)) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") @@ -129,15 +128,15 @@ private void setPds200SuccessState() { private void ehrRepository200Response() { stubPdsAdaptor.stubFor(delete(urlEqualTo("/patients/" + NHS_NUMBER)) - .withHeader("Authorization", matching(authKey)) + .withHeader("Authorization", equalTo(EXPECTED_EHR_REPO_AUTH_HEADER)) .willReturn(aResponse() .withStatus(200) + .withHeader("Content-Type", "application/json") .withBody("{\n" + " \"data\": {\n" + " \"type\": \"patients\",\n" + " \"id\": " + NHS_NUMBER + ",\n" + - " \"conversationIds\":[\"2431d4ff-f760-4ab9-8cd8-a3fc47846762\"," + "\"c184cc19-86e9-4a95-b5b5-2f156900bb3c\"]}}") - .withHeader("Content-Type", "application/json"))); + " \"conversationIds\":[\"2431d4ff-f760-4ab9-8cd8-a3fc47846762\"," + "\"c184cc19-86e9-4a95-b5b5-2f156900bb3c\"]}}"))); } private ReRegistrationEvent getReRegistrationEvent() { diff --git a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoServiceIntegrationTest.java b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoServiceIntegrationTest.java index b8d90f83..0332ec4e 100644 --- a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoServiceIntegrationTest.java +++ b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoServiceIntegrationTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; @@ -29,17 +28,14 @@ public class EhrRepoServiceIntegrationTest { @Autowired private EhrRepoService ehrRepoService; - @Value("${ehrRepoAuthKey}") - private String authKey; - private WireMockServer stubEhrRepo; public static final String NHS_NUMBER = "1234567890"; + private static final String AUTHORIZATION_HEADER = "test"; private static final String NEMS_MESSAGE_ID = "nemsMessageId"; public static final String CONVERSATION_ID1 = "2431d4ff-f760-4ab9-8cd8-a3fc47846762"; public static final String CONVERSATION_ID2 = "c184cc19-86e9-4a95-b5b5-2f156900bb3c"; - @BeforeEach public void setUp() { stubEhrRepo = initializeWebServer(); @@ -70,7 +66,7 @@ void shouldSendMessageWithActionOnAuditTopicWhenEhrRepoReturns200() throws JsonP private void ehrRepo200Response() { stubFor(delete(urlMatching("/patients/" + NHS_NUMBER)) - .withHeader("Authorization", matching(authKey)) + .withHeader("Authorization", equalTo(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withStatus(200) .withBody("{\n" + diff --git a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/infra/LocalStackAwsConfig.java b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/infra/LocalStackAwsConfig.java index 2b3c0357..99fdfc1c 100644 --- a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/infra/LocalStackAwsConfig.java +++ b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/infra/LocalStackAwsConfig.java @@ -28,6 +28,9 @@ import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest; import software.amazon.awssdk.services.sqs.model.QueueAttributeName; import software.amazon.awssdk.services.sqs.model.QueueDoesNotExistException; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.ParameterType; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; import java.net.URI; import java.util.ArrayList; @@ -38,6 +41,10 @@ @TestConfiguration public class LocalStackAwsConfig { + private static final Region LOCALSTACK_REGION = Region.EU_WEST_2; + + private static final StaticCredentialsProvider LOCALSTACK_CREDENTIALS = + StaticCredentialsProvider.create(AwsBasicCredentials.create("LSIA5678901234567890", "LSIA5678901234567890")); @Value("${aws.reRegistrationsQueueName}") private String reRegistrationsQueueName; @@ -57,18 +64,27 @@ public class LocalStackAwsConfig { @Autowired private SqsClient sqsClient; + @Autowired + private SsmClient ssmClient; + @Autowired private DynamoDbClient dynamoDbClient; @Value("${aws.activeSuspensionsDynamoDbTableName}") private String activeSuspensionsDynamoDbTableName; + @Value("${pdsAdaptor.authPasswordSsmParameterName}") + private String authPasswordSsmParameterName; + + @Value("${ehrRepoAuthKeySsmParameterName}") + private String ehrRepoAuthKeySsmParameterName; + @Bean public static SqsClient sqsClient(@Value("${localstack.url}") String localstackUrl) { return SqsClient.builder() - .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("FAKE", "FAKE"))) .endpointOverride(URI.create(localstackUrl)) - .region(Region.EU_WEST_2) + .region(LOCALSTACK_REGION) + .credentialsProvider(LOCALSTACK_CREDENTIALS) .build(); } @@ -76,18 +92,17 @@ public static SqsClient sqsClient(@Value("${localstack.url}") String localstackU public static SnsClient snsClient(@Value("${localstack.url}") String localstackUrl) { return SnsClient.builder() .endpointOverride(URI.create(localstackUrl)) - .region(Region.EU_WEST_2) - .credentialsProvider(StaticCredentialsProvider.create(new AwsCredentials() { - @Override - public String accessKeyId() { - return "FAKE"; - } - - @Override - public String secretAccessKey() { - return "FAKE"; - } - })) + .region(LOCALSTACK_REGION) + .credentialsProvider(LOCALSTACK_CREDENTIALS) + .build(); + } + + @Bean + public static SsmClient ssmClient(@Value("${localstack.url}") String localstackUrl) { + return SsmClient.builder() + .endpointOverride(URI.create(localstackUrl)) + .region(LOCALSTACK_REGION) + .credentialsProvider(LOCALSTACK_CREDENTIALS) .build(); } @@ -95,19 +110,8 @@ public String secretAccessKey() { public static DynamoDbClient dynamoDbClient(@Value("${localstack.url}") String localstackUrl) { return DynamoDbClient.builder() .endpointOverride(URI.create(localstackUrl)) - .region(Region.EU_WEST_2) - .credentialsProvider( - StaticCredentialsProvider.create(new AwsCredentials() { - @Override - public String accessKeyId() { - return "FAKE"; - } - - @Override - public String secretAccessKey() { - return "FAKE"; - } - })) + .region(LOCALSTACK_REGION) + .credentialsProvider(LOCALSTACK_CREDENTIALS) .build(); } @@ -121,9 +125,27 @@ public void setupTestQueuesTopicsAndDb() { createSnsTestReceiverSubscription(topic, getQueueArn(reRegistrationsAuditQueue.queueUrl())); + setupSsmParameters(); + setupDbAndTable(); } + private void setupSsmParameters() { + ssmClient.putParameter(PutParameterRequest.builder() + .name(authPasswordSsmParameterName) + .value("test") + .type(ParameterType.SECURE_STRING) + .overwrite(true) + .build()); + + ssmClient.putParameter(PutParameterRequest.builder() + .name(ehrRepoAuthKeySsmParameterName) + .value("test") + .type(ParameterType.SECURE_STRING) + .overwrite(true) + .build()); + } + private void setupDbAndTable() { var waiter = dynamoDbClient.waiter(); diff --git a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceIntegrationTest.java b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceIntegrationTest.java index 6084cb77..fc890892 100644 --- a/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceIntegrationTest.java +++ b/services/re-registration-service/src/integration/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceIntegrationTest.java @@ -49,6 +49,7 @@ public class PdsAdaptorServiceIntegrationTest { public static final String NHS_NUMBER = "1234567890"; public static final String STATUS_MESSAGE_FOR_WHEN_PATIENT_IS_STILL_SUSPENDED = "NO_ACTION:RE_REGISTRATION_FAILED_STILL_SUSPENDED"; public static final String STATUS_MESSAGE_FOR_WHEN_PDS_RETURNS_4XX_ERROR = "NO_ACTION:RE_REGISTRATION_FAILED_PDS_ERROR"; + private static final String EXPECTED_PDS_AUTH_HEADER = "Basic dXNlcm5hbWU6dGVzdA=="; @Autowired private SqsClient sqs; @@ -136,7 +137,7 @@ void shouldPutTheAuditStatusMessageOnAuditTopicWhenPdsReturnsResponseWithStatusC private void setPds200SuccessState(String startingState, int priority, String nhsNumber) { stubPdsAdaptor.stubFor(get(urlEqualTo("/suspended-patient-status/" + nhsNumber)).atPriority(priority) - .withHeader("Authorization", equalTo("Basic cmUtcmVnaXN0cmF0aW9uLXNlcnZpY2U6ZGVmYXVsdA==")) + .withHeader("Authorization", equalTo(EXPECTED_PDS_AUTH_HEADER)) .inScenario("Retry Scenario") .whenScenarioStateIs(startingState) .willReturn(aResponse() @@ -147,7 +148,7 @@ private void setPds200SuccessState(String startingState, int priority, String nh private void setPdsErrorState(String startingState, String finishedState, int priority, String nhsNumber, int statusCode) { stubPdsAdaptor.stubFor(get(urlEqualTo("/suspended-patient-status/" + nhsNumber)).atPriority(priority) - .withHeader("Authorization", equalTo("Basic cmUtcmVnaXN0cmF0aW9uLXNlcnZpY2U6ZGVmYXVsdA==")) + .withHeader("Authorization", equalTo(EXPECTED_PDS_AUTH_HEADER)) .inScenario("Retry Scenario") .whenScenarioStateIs(startingState) .willReturn(aResponse() diff --git a/services/re-registration-service/src/integration/resources/application-test.properties b/services/re-registration-service/src/integration/resources/application-test.properties index 1937f104..b09f373c 100644 --- a/services/re-registration-service/src/integration/resources/application-test.properties +++ b/services/re-registration-service/src/integration/resources/application-test.properties @@ -3,13 +3,15 @@ aws.reRegistrationsQueueName=test-re-registrations-queue aws.reRegistration.auditTopic.arn=arn:aws:sns:eu-west-2:000000000000:re_registration_audit_sns_topic localstack.url=${LOCALSTACK_URL:http://localhost:4566} pdsAdaptor.serviceUrl=http://localhost:8080 +pdsAdaptor.authUserName=username +pdsAdaptor.authPasswordSsmParameterName=auth-key-ssm-parameter-name pdsIntermittentError.retry.max.attempts=3 pdsIntermittentError.initial.interval.millisecond=1000 pdsIntermittentError.initial.interval.multiplier=2.0 toggle.canSendDeleteEhrRequest=true spring.main.allow-bean-definition-overriding=true ehrRepoUrl=http://localhost:8080 -ehrRepoAuthKey=auth-key-2 +ehrRepoAuthKeySsmParameterName=auth-key-ssm-parameter-name aws.reRegistrationsAuditQueueName=test-re-registrations-audit-queue aws.activeSuspensionsQueueName=test-active-suspensions aws.activeSuspensionsDynamoDbTableName=test-active-suspensions-dynamodb \ No newline at end of file diff --git a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/config/SsmClientSpringConfiguration.java b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/config/SsmClientSpringConfiguration.java new file mode 100644 index 00000000..7c5df157 --- /dev/null +++ b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/config/SsmClientSpringConfiguration.java @@ -0,0 +1,19 @@ +package uk.nhs.prm.repo.re_registration.config; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ssm.SsmClient; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class SsmClientSpringConfiguration { + @Bean + public SsmClient ssmClient() { + return SsmClient.builder() + .region(Region.EU_WEST_2) + .build(); + } +} diff --git a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoService.java b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoService.java index 4238ba0f..5e5315c4 100644 --- a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoService.java +++ b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoService.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -9,34 +10,47 @@ import uk.nhs.prm.repo.re_registration.http.HttpClient; import uk.nhs.prm.repo.re_registration.message_publishers.ReRegistrationAuditPublisher; import uk.nhs.prm.repo.re_registration.model.ReRegistrationEvent; +import uk.nhs.prm.repo.re_registration.service.SsmService; @Slf4j @Service public class EhrRepoService { - private final String ehrRepoUrl; - private final String ehrRepoAuthKey; private final Tracer tracer; private final ReRegistrationAuditPublisher auditPublisher; + private SsmService ssmService; private HttpClient httpClient; - - public EhrRepoService(@Value("${ehrRepoUrl}") String ehrRepoUrl, @Value("${ehrRepoAuthKey}") String ehrRepoAuthKey, Tracer tracer, ReRegistrationAuditPublisher auditPublisher, HttpClient httpClient) { - this.ehrRepoUrl = ehrRepoUrl; - this.ehrRepoAuthKey = ehrRepoAuthKey; + private final String ehrRepoUrl; + private final String ehrRepoAuthKeySsmParameterName; + private String ehrRepoAuthKey; + + public EhrRepoService( + Tracer tracer, + ReRegistrationAuditPublisher auditPublisher, + SsmService ssmService, + HttpClient httpClient, + @Value("${ehrRepoUrl}") String ehrRepoUrl, + @Value("${ehrRepoAuthKeySsmParameterName}") String ehrRepoAuthKeySsmParameterName + ) { this.tracer = tracer; this.auditPublisher = auditPublisher; + this.ssmService = ssmService; this.httpClient = httpClient; + this.ehrRepoUrl = ehrRepoUrl; + this.ehrRepoAuthKeySsmParameterName = ehrRepoAuthKeySsmParameterName; } - public EhrDeleteResponseContent deletePatientEhr(ReRegistrationEvent reRegistrationEvent) throws JsonProcessingException { + @PostConstruct + private void getEhrRepoAuthKeyFromSsm() { + ehrRepoAuthKey = ssmService.getValueForParameter(ehrRepoAuthKeySsmParameterName); + } + public EhrDeleteResponseContent deletePatientEhr(ReRegistrationEvent reRegistrationEvent) throws JsonProcessingException { var url = getPatientDeleteEhrUrl(reRegistrationEvent.getNhsNumber()); log.info("Making a DELETE EHR Request to ehr-repo"); var ehrRepoResponse = httpClient.delete(url, ehrRepoAuthKey); return getParsedDeleteEhrResponseBody(ehrRepoResponse.getBody()); - } - private EhrDeleteResponseContent getParsedDeleteEhrResponseBody(String responseBody) throws JsonProcessingException { log.info("Trying to parse ehr-repo response"); return new ObjectMapper().readValue(responseBody, EhrDeleteResponse.class).getEhrDeleteResponseContent(); diff --git a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorService.java b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorService.java index ae4a9987..01a7e806 100644 --- a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorService.java +++ b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorService.java @@ -1,32 +1,43 @@ package uk.nhs.prm.repo.re_registration.pds; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import uk.nhs.prm.repo.re_registration.http.HttpClient; import uk.nhs.prm.repo.re_registration.model.ReRegistrationEvent; import uk.nhs.prm.repo.re_registration.pds.model.PdsAdaptorSuspensionStatusResponse; +import uk.nhs.prm.repo.re_registration.service.SsmService; @Slf4j @Service public class PdsAdaptorService { + private SsmService ssmService; private HttpClient httpClient; private String pdsAdaptorServiceUrl; - private String authPassword; private String authUserName; + private String authPasswordSsmParameterName; + private String authPassword; - - public PdsAdaptorService(HttpClient httpClient, - @Value("${pdsAdaptor.serviceUrl}") String pdsAdaptorServiceUrl, - @Value("${pdsAdaptor.authUserName}") String authUserName, - @Value("${pdsAdaptor.authPassword}") String authPassword) { - + public PdsAdaptorService( + SsmService ssmService, + HttpClient httpClient, + @Value("${pdsAdaptor.serviceUrl}") String pdsAdaptorServiceUrl, + @Value("${pdsAdaptor.authUserName}") String authUserName, + @Value("${pdsAdaptor.authPasswordSsmParameterName}") String authPasswordSsmParameterName + ) { + this.ssmService = ssmService; this.httpClient = httpClient; this.pdsAdaptorServiceUrl = pdsAdaptorServiceUrl; this.authUserName = authUserName; - this.authPassword = authPassword; + this.authPasswordSsmParameterName = authPasswordSsmParameterName; + } + + @PostConstruct + private void getAuthPasswordFromSsm() { + authPassword = ssmService.getValueForParameter(authPasswordSsmParameterName); } public PdsAdaptorSuspensionStatusResponse getPatientPdsStatus(ReRegistrationEvent reRegistrationEvent) { diff --git a/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/service/SsmService.java b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/service/SsmService.java new file mode 100644 index 00000000..9413a75d --- /dev/null +++ b/services/re-registration-service/src/main/java/uk/nhs/prm/repo/re_registration/service/SsmService.java @@ -0,0 +1,35 @@ +package uk.nhs.prm.repo.re_registration.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.Parameter; + +@Slf4j +@Service +public class SsmService { + + private final SsmClient ssmClient; + + @Autowired + public SsmService(SsmClient ssmClient) { + this.ssmClient = ssmClient; + } + + public String getValueForParameter(String parameterName) { + log.info("Getting value for SSM parameter: {}", parameterName); + + GetParameterRequest parameterRequest = GetParameterRequest.builder() + .name(parameterName) + .withDecryption(true) + .build(); + + Parameter parameter = ssmClient.getParameter(parameterRequest).parameter(); + + log.info("Value for SSM parameter {} retrieved successfully. Parameter version is: {}", parameterName, parameter.version()); + + return parameter.value(); + } +} diff --git a/services/re-registration-service/src/main/resources/application.properties b/services/re-registration-service/src/main/resources/application.properties index 8ab602b0..950b2270 100644 --- a/services/re-registration-service/src/main/resources/application.properties +++ b/services/re-registration-service/src/main/resources/application.properties @@ -6,7 +6,7 @@ reRegistrations.concurrency.max.messages.per.task=${CONCURRENCY_MAX_MESSAGES_PER reRegistrations.thread.core.pool.size=${THREAD_CORE_POOL_SIZE:5} reRegistrations.thread.max.pool.size=${THREAD_MAX_POOL_SIZE:8} pdsAdaptor.serviceUrl=${PDS_ADAPTOR_SERVICE_URL:default} -pdsAdaptor.authPassword=${PDS_ADAPTOR_AUTH_PASSWORD:default} +pdsAdaptor.authPasswordSsmParameterName=${PDS_ADAPTOR_AUTH_PASSWORD_SSM_PARAMETER_NAME:default} pdsAdaptor.authUserName= re-registration-service aws.reRegistration.auditTopic.arn=${RE_REGISTRATIONS_AUDIT_SNS_TOPIC_ARN:default} pdsIntermittentError.retry.max.attempts=7 @@ -14,7 +14,7 @@ pdsIntermittentError.initial.interval.millisecond=1000 pdsIntermittentError.initial.interval.multiplier=2.0 toggle.canSendDeleteEhrRequest=${CAN_SEND_DELETE_EHR_REQUEST:false} ehrRepoUrl=${EHR_REPO_URL} -ehrRepoAuthKey=${RE_REGISTRATION_SERVICE_AUTHORIZATION_KEYS_FOR_EHR_REPO} +ehrRepoAuthKeySsmParameterName=${RE_REGISTRATION_SERVICE_AUTHORIZATION_KEYS_FOR_EHR_REPO_SSM_PARAMETER_NAME} aws.reRegistrationsAuditQueueName=${RE_REGISTRATIONS_AUDIT_QUEUE_NAME} aws.activeSuspensionsQueueName=${ACTIVE_SUSPENSIONS_QUEUE_NAME} aws.activeSuspensionsDynamoDbTableName=${ACTIVE_SUSPENSIONS_DYNAMODB_TABLE_NAME} \ No newline at end of file diff --git a/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoClientTest.java b/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoClientTest.java index 6a2472b8..69ff1097 100644 --- a/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoClientTest.java +++ b/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/ehr_repo/EhrRepoClientTest.java @@ -14,6 +14,7 @@ import uk.nhs.prm.repo.re_registration.http.HttpClient; import uk.nhs.prm.repo.re_registration.message_publishers.ReRegistrationAuditPublisher; import uk.nhs.prm.repo.re_registration.model.ReRegistrationEvent; +import uk.nhs.prm.repo.re_registration.service.SsmService; import java.net.MalformedURLException; import java.util.Arrays; @@ -23,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import static org.springframework.test.util.ReflectionTestUtils.setField; @ExtendWith(MockitoExtension.class) class EhrRepoClientTest { @@ -33,29 +35,32 @@ class EhrRepoClientTest { static UUID traceId = UUID.randomUUID(); + @Mock + SsmService ssmService; + @Mock HttpClient httpClient; EhrRepoService ehrRepoService; - @Captor ArgumentCaptor url; @Captor ArgumentCaptor authKey; - private String ehrRepoServiceUrl = "ehr-repo-service-url"; - private String ehrRepoAuthKey = "authKey"; + private String ehrRepoAuthKeySsmParameterName = "authKeySsmParameterName"; @BeforeEach void init() throws MalformedURLException { - ehrRepoService = new EhrRepoService(ehrRepoServiceUrl, ehrRepoAuthKey, tracer, reRegistrationAuditPublisher, httpClient); + ehrRepoService = new EhrRepoService(tracer, reRegistrationAuditPublisher, ssmService, httpClient, ehrRepoServiceUrl, ehrRepoAuthKeySsmParameterName); + // normally retrieved from SSM post-construction, but set directly here for testing + setField(ehrRepoService, "ehrRepoAuthKey", "authKey"); } @Test - void shouldCallHttpClientWithCorrectUriAndUserNAmeAndPassword() throws JsonProcessingException { + void shouldCallHttpClientWithCorrectUriAndUserNameAndPassword() throws JsonProcessingException { when(httpClient.delete(any(), any())).thenReturn(createDeleteEhrResponseJsonString()); ehrRepoService.deletePatientEhr(getReRegistrationEvent()); verify(httpClient).delete(url.capture(), authKey.capture()); diff --git a/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceTest.java b/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceTest.java index 8e17340b..11d7df0c 100644 --- a/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceTest.java +++ b/services/re-registration-service/src/test/java/uk/nhs/prm/repo/re_registration/pds/PdsAdaptorServiceTest.java @@ -12,16 +12,21 @@ import uk.nhs.prm.repo.re_registration.http.HttpClient; import uk.nhs.prm.repo.re_registration.model.ReRegistrationEvent; import uk.nhs.prm.repo.re_registration.pds.model.PdsAdaptorSuspensionStatusResponse; +import uk.nhs.prm.repo.re_registration.service.SsmService; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.util.ReflectionTestUtils.setField; @ExtendWith(MockitoExtension.class) class PdsAdaptorServiceTest { + @Mock + SsmService ssmService; + @Mock HttpClient httpClient; @@ -38,15 +43,17 @@ class PdsAdaptorServiceTest { private String pdsAdaptorServiceUrl = "pds-service-url"; private String authUserName = "username"; - private String authPassword = "password"; + private String authPasswordSsmParameterName = "passwordSsmParameterName"; @BeforeEach void init() { - pdsAdaptorService = new PdsAdaptorService(httpClient, pdsAdaptorServiceUrl, authUserName, authPassword); + pdsAdaptorService = new PdsAdaptorService(ssmService, httpClient, pdsAdaptorServiceUrl, authUserName, authPasswordSsmParameterName); + // normally retrieved from SSM post-construction, but set directly here for testing + setField(pdsAdaptorService, "authPassword", "password"); } @Test - void shouldCallHttpClientWithCorrectUriAndUserNAmeAndPassword() { + void shouldCallHttpClientWithCorrectUriAndUserNameAndPassword() { when(httpClient.get(any(), any(), any())).thenReturn(getPdsResponseStringWithSuspendedStatus(false)); pdsAdaptorService.getPatientPdsStatus(getReRegistrationEvent()); verify(httpClient).get(url.capture(), username.capture(), password.capture()); diff --git a/services/re-registration-service/src/test/resources/application.properties b/services/re-registration-service/src/test/resources/application.properties index 1ddf93f7..08a8f530 100644 --- a/services/re-registration-service/src/test/resources/application.properties +++ b/services/re-registration-service/src/test/resources/application.properties @@ -3,4 +3,4 @@ aws.region = ${AWS_REGION:eu-west-2} toggle.canSendDeleteEhrRequest=${CAN_SEND_DELETE_EHR_REQUEST:false} aws.reRegistrationsAuditQueueName=test-re-registrations-audit-queue ehrRepoUrl=:http://localhost:8080 -ehrRepoAuthKey=auth-key-2 \ No newline at end of file +ehrRepoAuthKeySsmParameterName=auth-key-2 \ No newline at end of file diff --git a/services/suspension-service/build.gradle b/services/suspension-service/build.gradle index 7e2c0680..dbcbc0c0 100644 --- a/services/suspension-service/build.gradle +++ b/services/suspension-service/build.gradle @@ -47,6 +47,7 @@ dependencies { implementation 'software.amazon.awssdk:cloudwatch' implementation 'software.amazon.awssdk:sns' implementation 'software.amazon.awssdk:sqs' + implementation 'software.amazon.awssdk:ssm' implementation 'software.amazon.awssdk:dynamodb' implementation 'com.amazonaws:amazon-sqs-java-messaging-lib:2.1.4' diff --git a/services/suspension-service/docker-compose.localstack-local.yaml b/services/suspension-service/docker-compose.localstack-local.yaml index 1d11e6e6..c0cee7bb 100644 --- a/services/suspension-service/docker-compose.localstack-local.yaml +++ b/services/suspension-service/docker-compose.localstack-local.yaml @@ -7,7 +7,7 @@ services: network_mode: bridge environment: - DOCKER_HOST=unix:///var/run/docker.sock - - SERVICES=sqs,sns,dynamodb,cloudwatch + - SERVICES=sqs,sns,ssm,dynamodb,cloudwatch - GATEWAY_LISTEN=4566 volumes: - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/services/suspension-service/docker-compose.yaml b/services/suspension-service/docker-compose.yaml deleted file mode 100644 index 294f0470..00000000 --- a/services/suspension-service/docker-compose.yaml +++ /dev/null @@ -1,15 +0,0 @@ -services: - default: - links: - - localstack:localstack - localstack: - image: localstack/localstack - ports: - - "4566:4566" - environment: - - SERVICES=sqs,sns,dynamodb - - DEFAULT_REGION=eu-west-2 - - HOSTNAME_EXTERNAL=localstack - volumes: - - "/var/lib/localstack:/var/lib/localstack" - - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/infra/LocalStackAwsConfig.java b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/infra/LocalStackAwsConfig.java index 7c6851d9..9c65ae68 100644 --- a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/infra/LocalStackAwsConfig.java +++ b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/infra/LocalStackAwsConfig.java @@ -22,6 +22,10 @@ import software.amazon.awssdk.services.sqs.model.QueueAttributeName; import jakarta.annotation.PostConstruct; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.ParameterType; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; + import java.net.URI; import java.util.ArrayList; import java.util.HashMap; @@ -30,7 +34,6 @@ @TestConfiguration public class LocalStackAwsConfig { - private static final Region LOCALSTACK_REGION = Region.EU_WEST_2; private static final StaticCredentialsProvider LOCALSTACK_CREDENTIALS = @@ -42,6 +45,9 @@ public class LocalStackAwsConfig { @Autowired private SnsClient snsClient; + @Autowired + private SsmClient ssmClient; + @Autowired private DynamoDbClient dynamoDbClient; @@ -75,6 +81,9 @@ public class LocalStackAwsConfig { @Value("${aws.activeSuspensionsQueueName}") private String activeSuspensionsQueueName; + @Value("${pdsAdaptor.suspensionService.passwordSsmParameterName}") + private String authPasswordSsmParameterName; + @Bean public static SqsClient sqsClient(@Value("${localstack.url}") String localstackUrl) { return SqsClient.builder() @@ -93,6 +102,15 @@ public static SnsClient snsClient(@Value("${localstack.url}") String localstackU .build(); } + @Bean + public static SsmClient ssmClient(@Value("${localstack.url}") String localstackUrl) { + return SsmClient.builder() + .endpointOverride(URI.create(localstackUrl)) + .region(LOCALSTACK_REGION) + .credentialsProvider(LOCALSTACK_CREDENTIALS) + .build(); + } + @Bean public static DynamoDbClient dynamoDbClient(@Value("${localstack.url}") String localstackUrl) { return DynamoDbClient.builder() @@ -113,7 +131,7 @@ public static CloudWatchClient cloudwatchClient(@Value("${localstack.url}") Stri } @PostConstruct - public void setupTestQueuesAndTopics() { + public void initialSetup() { sqsClient.createQueue(builder -> builder.queueName(suspensionsQueueName)); sqsClient.createQueue(builder -> builder.queueName(ackQueueName)); CreateQueueResponse notSuspendedQueue = sqsClient.createQueue(builder -> builder.queueName(notSuspendedQueueName)); @@ -141,9 +159,20 @@ public void setupTestQueuesAndTopics() { createSnsTestReceiverSubscription(repoIncomingTopic, getQueueArn(incomingQueue.queueUrl())); createSnsTestReceiverSubscription(activeSuspensionsTopic, getQueueArn(activeSuspensionsQueue.queueUrl())); + setupSsmParameters(); + setupDbAndTable(); } + private void setupSsmParameters() { + ssmClient.putParameter(PutParameterRequest.builder() + .name(authPasswordSsmParameterName) + .value("test") + .type(ParameterType.SECURE_STRING) + .overwrite(true) + .build()); + } + private void setupDbAndTable() { var waiter = dynamoDbClient.waiter(); var tableRequest = DescribeTableRequest.builder() @@ -180,6 +209,7 @@ private void setupDbAndTable() { waiter.waitUntilTableExists(tableRequest); } + private void resetTableForLocalEnvironment(DynamoDbWaiter waiter, DescribeTableRequest tableRequest) { var deleteRequest = DeleteTableRequest.builder().tableName(suspensionDynamoDbTableName).build(); dynamoDbClient.deleteTable(deleteRequest); diff --git a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateBasedOnOdsCodeToggle.java b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateBasedOnOdsCodeToggle.java index e4e31ab9..de8d2554 100644 --- a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateBasedOnOdsCodeToggle.java +++ b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateBasedOnOdsCodeToggle.java @@ -34,6 +34,7 @@ @ContextConfiguration(classes = LocalStackAwsConfig.class) @DirtiesContext public class MOFUpdateBasedOnOdsCodeToggle { + private static final String AUTHORIZATION_HEADER = "Basic c3VzcGVuc2lvbi1zZXJ2aWNlOnRlc3Q="; @Autowired private SqsClient sqs; @@ -111,12 +112,12 @@ void shouldSetMOFAsRepoOdsCodeWhenOdsCodeIsInSafeList(){ private void stubForPdsAdaptor(String nhsNumber, String suspendedResponse) { stubPdsAdaptor.stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); stubPdsAdaptor.stubFor(put(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(suspendedResponse))); diff --git a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateToRepoIntegrationTest.java b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateToRepoIntegrationTest.java index 932b8052..9d0a788e 100644 --- a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateToRepoIntegrationTest.java +++ b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/repo/in/MOFUpdateToRepoIntegrationTest.java @@ -33,6 +33,7 @@ @ContextConfiguration(classes = LocalStackAwsConfig.class) @DirtiesContext public class MOFUpdateToRepoIntegrationTest { + private static final String AUTHORIZATION_HEADER = "Basic c3VzcGVuc2lvbi1zZXJ2aWNlOnRlc3Q="; @Autowired private SqsClient sqs; @@ -73,12 +74,12 @@ private WireMockServer initializeWebServer() { void shouldSetMOFAsRepoOdsCodeWhenToggleOn() { var nhsNumber = Long.toString(System.currentTimeMillis()); stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); stubFor(put(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWithRepoOdsCode(nhsNumber)))); diff --git a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionThrottlingTest.java b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionThrottlingTest.java index ae96b5e7..b9decb99 100644 --- a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionThrottlingTest.java +++ b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionThrottlingTest.java @@ -1,10 +1,7 @@ package uk.nhs.prm.repo.suspension.service.suspensionsevents; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,6 +39,7 @@ @ContextConfiguration(classes = LocalStackAwsConfig.class) @DirtiesContext public class SuspensionThrottlingTest { + private static final String AUTHORIZATION_HEADER = "Basic c3VzcGVuc2lvbi1zZXJ2aWNlOnRlc3Q="; @Autowired private SqsClient sqs; @@ -164,7 +162,7 @@ private void setPdsRetryMessage(String nhsNumber) { setPdsErrorState("Second Cause Success", "Third Cause Success", 3, nhsNumber); stubFor(get(urlEqualTo("/suspended-patient-status/" + nhsNumber)).atPriority(4) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .inScenario("Retry Scenario") .whenScenarioStateIs("Third Cause Success") .willReturn(aResponse() @@ -175,7 +173,7 @@ private void setPdsRetryMessage(String nhsNumber) { private void setPdsErrorState(String startingState, String finishedState, int priority, String nhsNumber) { stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)).atPriority(priority) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .inScenario("Retry Scenario") .whenScenarioStateIs(startingState) .willReturn(aResponse() @@ -194,14 +192,14 @@ private void sendMultipleBatchesOf10Messages(String queueUrl, int numberOfBatche private void stubbinForGenericPdsResponses(int getRequestDelay, int putRequestDelay) { var anyNhsNumber = ".*"; stubFor(get(urlMatching("/suspended-patient-status/" + anyNhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withStatus(200) .withFixedDelay(getRequestDelay) .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponse()))); stubFor(put(urlMatching("/suspended-patient-status/" + anyNhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withStatus(200) .withFixedDelay(putRequestDelay) diff --git a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionsIntegrationTest.java b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionsIntegrationTest.java index 7b14a5e3..f82ae93b 100644 --- a/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionsIntegrationTest.java +++ b/services/suspension-service/src/integration/java/uk/nhs/prm/repo/suspension/service/suspensionsevents/SuspensionsIntegrationTest.java @@ -32,6 +32,7 @@ @ContextConfiguration(classes = LocalStackAwsConfig.class) @DirtiesContext public class SuspensionsIntegrationTest { + private static final String AUTHORIZATION_HEADER = "Basic c3VzcGVuc2lvbi1zZXJ2aWNlOnRlc3Q="; @Autowired private SqsClient sqs; @@ -87,7 +88,7 @@ private WireMockServer initializeWebServer() { void shouldSendSuspensionMessageToNotSuspendedSNSTopicIfNoLongerSuspendedInPDS() { var nhsNumber = Long.toString(System.currentTimeMillis()); stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getNotSuspendedResponseWith(nhsNumber)))); @@ -109,12 +110,12 @@ void shouldSendSuspensionMessageToNotSuspendedSNSTopicIfNoLongerSuspendedInPDS() void shouldUpdateManagingOrganisationAndSendMessageToMofUpdatedSNSTopicForSuspendedPatient() { var nhsNumber = Long.toString(System.currentTimeMillis()); stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); stubFor(put(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); @@ -151,12 +152,12 @@ void shouldPutEventOutOfOrderInRelevantQueues() { var nhsNumber = Long.toString(System.currentTimeMillis()); stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); stubFor(put(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); @@ -191,12 +192,12 @@ void shouldPutEventOutOfOrderInRelevantQueues() { void shouldPutDLQsWhenPdsAdaptorReturn400() { var nhsNumber = Long.toString(System.currentTimeMillis()); stubFor(get(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(getSuspendedResponseWith(nhsNumber)))); stubFor(put(urlMatching("/suspended-patient-status/" + nhsNumber)) - .withHeader("Authorization", matching("Basic c3VzcGVuc2lvbi1zZXJ2aWNlOiJ0ZXN0Ig==")) + .withHeader("Authorization", matching(AUTHORIZATION_HEADER)) .willReturn(aResponse() .withStatus(400) .withHeader("Content-Type", "application/json"))); diff --git a/services/suspension-service/src/integration/resources/application-test.properties b/services/suspension-service/src/integration/resources/application-test.properties index 02f9dcfb..754889a4 100644 --- a/services/suspension-service/src/integration/resources/application-test.properties +++ b/services/suspension-service/src/integration/resources/application-test.properties @@ -15,7 +15,7 @@ aws.activeSuspensionsSnsTopicArn=arn:aws:sns:eu-west-2:000000000000:active_suspe aws.region=eu-west-2 spring.main.allow-bean-definition-overriding=true pdsAdaptor.serviceUrl=http://localhost:8080 -pdsAdaptor.suspensionService.password="test" +pdsAdaptor.suspensionService.passwordSsmParameterName=test process_only_synthetic_patients=false synthetic_patient_prefix=991 localstack.url=${LOCALSTACK_URL:http://localhost:4566} diff --git a/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/config/SsmClientSpringConfiguration.java b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/config/SsmClientSpringConfiguration.java new file mode 100644 index 00000000..b32c79e0 --- /dev/null +++ b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/config/SsmClientSpringConfiguration.java @@ -0,0 +1,18 @@ +package uk.nhs.prm.repo.suspension.service.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ssm.SsmClient; + +@Configuration +@RequiredArgsConstructor +public class SsmClientSpringConfiguration { + @Bean + public SsmClient ssmClient() { + return SsmClient.builder() + .region(Region.EU_WEST_2) + .build(); + } +} diff --git a/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/pds/PdsService.java b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/pds/PdsService.java index d033e582..9acaa2b8 100644 --- a/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/pds/PdsService.java +++ b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/pds/PdsService.java @@ -1,5 +1,6 @@ package uk.nhs.prm.repo.suspension.service.pds; +import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -8,27 +9,44 @@ import uk.nhs.prm.repo.suspension.service.http.RateLimitHttpClient; import uk.nhs.prm.repo.suspension.service.model.PdsAdaptorSuspensionStatusResponse; import uk.nhs.prm.repo.suspension.service.model.UpdateManagingOrganisationRequest; +import uk.nhs.prm.repo.suspension.service.service.SsmService; @Component @Slf4j public class PdsService { - public static final String SUSPENSION_SERVICE_USERNAME = "suspension-service"; - private static final String SUSPENDED_PATIENT = "suspended-patient-status/"; - @Value("${pdsAdaptor.suspensionService.password}") - private String suspensionServicePassword; + private static final String SUSPENDED_PATIENT = "suspended-patient-status/"; - @Value("${pdsAdaptor.serviceUrl}") - private String serviceUrl; + private final SsmService ssmService; private final PdsAdaptorSuspensionStatusResponseParser responseParser; private final RateLimitHttpClient httpClient; - public PdsService(PdsAdaptorSuspensionStatusResponseParser responseParser, RateLimitHttpClient httpClient) { + private String serviceUrl; + + private String suspensionServicePasswordSsmParameterName; + + private String suspensionServicePassword; + + public PdsService( + SsmService ssmService, + PdsAdaptorSuspensionStatusResponseParser responseParser, + RateLimitHttpClient httpClient, + @Value("${pdsAdaptor.serviceUrl}") String serviceUrl, + @Value("${pdsAdaptor.suspensionService.passwordSsmParameterName}") String suspensionServicePasswordSsmParameterName + ) { + this.ssmService = ssmService; this.responseParser = responseParser; this.httpClient = httpClient; + this.serviceUrl = serviceUrl; + this.suspensionServicePasswordSsmParameterName = suspensionServicePasswordSsmParameterName; + } + + @PostConstruct + private void getAuthPasswordFromSsm() { + suspensionServicePassword = ssmService.getValueForParameter(suspensionServicePasswordSsmParameterName); } public PdsAdaptorSuspensionStatusResponse isSuspended(String nhsNumber) { diff --git a/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/service/SsmService.java b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/service/SsmService.java new file mode 100644 index 00000000..f34c2ffa --- /dev/null +++ b/services/suspension-service/src/main/java/uk/nhs/prm/repo/suspension/service/service/SsmService.java @@ -0,0 +1,35 @@ +package uk.nhs.prm.repo.suspension.service.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.Parameter; + +@Slf4j +@Service +public class SsmService { + + private final SsmClient ssmClient; + + @Autowired + public SsmService(SsmClient ssmClient) { + this.ssmClient = ssmClient; + } + + public String getValueForParameter(String parameterName) { + log.info("Getting value for SSM parameter: {}", parameterName); + + GetParameterRequest parameterRequest = GetParameterRequest.builder() + .name(parameterName) + .withDecryption(true) + .build(); + + Parameter parameter = ssmClient.getParameter(parameterRequest).parameter(); + + log.info("Value for SSM parameter {} retrieved successfully. Parameter version is: {}", parameterName, parameter.version()); + + return parameter.value(); + } +} diff --git a/services/suspension-service/src/main/resources/application.properties b/services/suspension-service/src/main/resources/application.properties index 7c165d18..4836e6c6 100644 --- a/services/suspension-service/src/main/resources/application.properties +++ b/services/suspension-service/src/main/resources/application.properties @@ -17,7 +17,7 @@ aws.eventOutOrderSnsTopicArn=${EVENT_OUT_OF_ORDER_SNS_TOPIC_ARN} aws.suspensionDynamoDbTableName=${DYNAMODB_TABLE_NAME} aws.repoIncomingSnsTopicArn=${REPO_INCOMING_SNS_TOPIC_ARN} pdsAdaptor.serviceUrl=${PDS_ADAPTOR_URL} -pdsAdaptor.suspensionService.password=${PDS_ADAPTOR_SUSPENSION_SERVICE_PASSWORD} +pdsAdaptor.suspensionService.passwordSsmParameterName=${PDS_ADAPTOR_SUSPENSION_SERVICE_PASSWORD_SSM_PARAMETER_NAME} suspension.processor.initial.interval.millisecond=${INITIAL_INTERVAL_MILLISECOND:1000} suspension.processor.retry.max.attempts=${RETRY_MAX_ATTEMPTS:7} suspension.processor.initial.interval.multiplier=${RETRY_MULTIPLIER:2.0} diff --git a/services/suspension-service/src/test/java/uk/nhs/prm/repo/suspension/service/pds/PdsServiceTest.java b/services/suspension-service/src/test/java/uk/nhs/prm/repo/suspension/service/pds/PdsServiceTest.java index 4cee49f6..ab3e0e76 100644 --- a/services/suspension-service/src/test/java/uk/nhs/prm/repo/suspension/service/pds/PdsServiceTest.java +++ b/services/suspension-service/src/test/java/uk/nhs/prm/repo/suspension/service/pds/PdsServiceTest.java @@ -1,7 +1,5 @@ package uk.nhs.prm.repo.suspension.service.pds; -import ch.qos.logback.classic.spi.ILoggingEvent; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,6 +12,7 @@ import uk.nhs.prm.repo.suspension.service.http.RateLimitHttpClient; import uk.nhs.prm.repo.suspension.service.model.PdsAdaptorSuspensionStatusResponse; import uk.nhs.prm.repo.suspension.service.model.UpdateManagingOrganisationRequest; +import uk.nhs.prm.repo.suspension.service.service.SsmService; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -24,19 +23,25 @@ @ExtendWith(MockitoExtension.class) class PdsServiceTest { - @Mock - private RateLimitHttpClient client; + private SsmService ssmService; @Mock private PdsAdaptorSuspensionStatusResponseParser responseParser; + @Mock + private RateLimitHttpClient client; + private PdsService pdsService; + private String serviceUrl = "http://pds-adaptor"; + + private String suspensionServicePasswordSsmParameterName = "passwordSsmParameterName"; + @BeforeEach public void setUp() { - pdsService = new PdsService(responseParser, client); - setField(pdsService, "serviceUrl", "http://pds-adaptor"); // @todo fertling :/ + pdsService = new PdsService(ssmService, responseParser, client, serviceUrl, suspensionServicePasswordSsmParameterName); + // normally retrieved from SSM post-construction, but set directly here for testing setField(pdsService, "suspensionServicePassword", "PASS"); }