From fc066d1ddc36687e81bf0f9f2ad13632320028f1 Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Fri, 24 Mar 2023 16:11:34 +0000 Subject: [PATCH 1/9] feat: AWS xray tracing config TIS21-4226 --- .aws/task-definition-template.json | 6 ++- README.md | 6 +++ build.gradle | 7 ++- .../credentials/api/IssueResource.java | 2 + .../credentials/api/VerifyResource.java | 2 + .../config/AwsXrayConfiguration.java | 40 ++++++++++++++++ .../config/AwsXrayInterceptor.java | 42 +++++++++++++++++ .../credentials/service/GatewayService.java | 2 + .../credentials/service/IssuanceService.java | 2 + .../credentials/service/JwtService.java | 2 + .../service/RevocationService.java | 2 + .../service/VerificationService.java | 2 + src/main/resources/application.yml | 9 +++- .../config/AwsXrayConfigurationTest.java | 47 +++++++++++++++++++ 14 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java create mode 100644 src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java create mode 100644 src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java diff --git a/.aws/task-definition-template.json b/.aws/task-definition-template.json index a924197e..689e5923 100644 --- a/.aws/task-definition-template.json +++ b/.aws/task-definition-template.json @@ -4,6 +4,10 @@ "name": "tis-trainee-credentials", "image": "430723991443.dkr.ecr.eu-west-2.amazonaws.com/tis-trainee-credentials:latest", "secrets": [ + { + "name": "AWS_XRAY_DAEMON_ADDRESS", + "valueFrom": "/tis/monitoring/xray/daemon-host" + }, { "name": "DB_HOST", "valueFrom": "/tis/trainee/${environment}/db/host" @@ -116,7 +120,7 @@ "value": "eu-west-2" }, { - "name": "SENTRY_ENVIRONMENT", + "name": "ENVIRONMENT", "value": "${environment}" } ] diff --git a/README.md b/README.md index c343c106..284752b3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # TIS Trainee Credentials ## About + This service issues and verifies trainee digital credentials. ## Developing @@ -17,6 +18,7 @@ gradlew bootRun | Name | Description | Default | |-----------------------------------|-------------------------------------------------------------------------------------------------------------------------|-------------| +| AWS_XRAY_DAEMON_ADDRESS | The AWS XRay daemon host. | | | DB_HOST | The MongoDB host to connect to. | localhost | | DB_PORT | The port to connect to MongoDB on. | 27017 | | DB_NAME | The name of the MongoDB database. | credentials | @@ -38,12 +40,14 @@ gradlew bootRun The Gradle `test` task can be used to run automated tests and produce coverage reports. + ```shell gradlew test ``` The Gradle `check` lifecycle task can be used to run automated tests and also verify formatting conforms to the code style guidelines. + ```shell gradlew check ``` @@ -55,7 +59,9 @@ gradlew bootBuildImage ``` ## Versioning + This project uses [Semantic Versioning](semver.org). ## License + This project is license under [The MIT License (MIT)](LICENSE). diff --git a/build.gradle b/build.gradle index ca9c447c..5aa3821c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } group = "uk.nhs.tis.trainee" -version = "0.16.0" +version = "0.17.0" configurations { compileOnly { @@ -51,6 +51,10 @@ dependencies { implementation "io.awspring.cloud:spring-cloud-starter-aws" implementation "io.awspring.cloud:spring-cloud-aws-messaging" + //AWS X-ray + implementation "com.amazonaws:aws-xray-recorder-sdk-spring:2.13.0" + compileOnly "javax.servlet:javax.servlet-api:4.0.1" + implementation "commons-codec:commons-codec:1.16.0" ext.jjwtVersion = "0.11.5" @@ -61,6 +65,7 @@ dependencies { testImplementation "org.springframework.cloud:spring-cloud-starter-bootstrap" testImplementation "com.playtika.testcontainers:embedded-redis:2.3.2" testImplementation "org.testcontainers:junit-jupiter:1.18.3" + testImplementation "javax.servlet:javax.servlet-api:4.0.1" } dependencyManagement { diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/IssueResource.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/IssueResource.java index 3c960434..725ed0b6 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/IssueResource.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/IssueResource.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.api; +import com.amazonaws.xray.spring.aop.XRayEnabled; import java.net.URI; import java.util.Optional; import java.util.UUID; @@ -50,6 +51,7 @@ @Slf4j @RestController() @RequestMapping("/api/issue") +@XRayEnabled public class IssueResource { private final IssuanceService service; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java index 99a4d894..672b3852 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.api; +import com.amazonaws.xray.spring.aop.XRayEnabled; import jakarta.validation.Valid; import java.net.URI; import lombok.extern.slf4j.Slf4j; @@ -46,6 +47,7 @@ @RestController @RequestMapping("/api/verify") @Validated +@XRayEnabled public class VerifyResource { private final VerificationService service; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java new file mode 100644 index 00000000..8020c800 --- /dev/null +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright 2023 Crown Copyright (Health Education England) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package uk.nhs.hee.tis.trainee.credentials.config; + +import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; +import javax.servlet.Filter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnExpression("!T(org.springframework.util.StringUtils)" + + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") +public class AwsXrayConfiguration { + + @Bean + public Filter tracingFilter(@Value("${application.environment}") String environment) { + return new AWSXRayServletFilter("tis-trainee-credentials-" + environment); + } +} diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java new file mode 100644 index 00000000..ff52001a --- /dev/null +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java @@ -0,0 +1,42 @@ +/* + * The MIT License (MIT) + * + * Copyright 2023 Crown Copyright (Health Education England) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package uk.nhs.hee.tis.trainee.credentials.config; + +import com.amazonaws.xray.spring.aop.AbstractXRayInterceptor; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@ConditionalOnExpression("!T(org.springframework.util.StringUtils)" + + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") +public class AwsXrayInterceptor extends AbstractXRayInterceptor { + + @Override + @Pointcut( + "@within(com.amazonaws.xray.spring.aop.XRayEnabled) && (bean(*Resource) || bean(*Service))") + public void xrayEnabledClasses() { + + } +} diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/GatewayService.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/GatewayService.java index 5bd9e267..272825f6 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/GatewayService.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/GatewayService.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.service; +import com.amazonaws.xray.spring.aop.XRayEnabled; import com.fasterxml.jackson.annotation.JsonProperty; import io.jsonwebtoken.Claims; import io.jsonwebtoken.impl.DefaultClaims; @@ -50,6 +51,7 @@ */ @Slf4j @Service +@XRayEnabled public class GatewayService { private static final String CLIENT_ID = "client_id"; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/IssuanceService.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/IssuanceService.java index 4f0da24b..e58fed52 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/IssuanceService.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/IssuanceService.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.service; +import com.amazonaws.xray.spring.aop.XRayEnabled; import io.jsonwebtoken.Claims; import java.net.URI; import java.time.Instant; @@ -45,6 +46,7 @@ */ @Slf4j @Service +@XRayEnabled public class IssuanceService { private final GatewayService gatewayService; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/JwtService.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/JwtService.java index 684b1342..11459d43 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/JwtService.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/JwtService.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.service; +import com.amazonaws.xray.spring.aop.XRayEnabled; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtParser; @@ -41,6 +42,7 @@ */ @Slf4j @Service +@XRayEnabled public class JwtService { private final ObjectMapper mapper; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/RevocationService.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/RevocationService.java index 88e6b2f7..a1a3779d 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/RevocationService.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/RevocationService.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.service; +import com.amazonaws.xray.spring.aop.XRayEnabled; import java.time.Instant; import java.util.List; import java.util.Optional; @@ -39,6 +40,7 @@ */ @Slf4j @Service +@XRayEnabled public class RevocationService { private final CredentialMetadataRepository credentialMetadataRepository; diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/VerificationService.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/VerificationService.java index a72fb0af..d106498b 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/VerificationService.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/service/VerificationService.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.service; +import com.amazonaws.xray.spring.aop.XRayEnabled; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import java.net.URI; @@ -45,6 +46,7 @@ */ @Slf4j @Service +@XRayEnabled public class VerificationService { private static final String CREDENTIAL_PREFIX = "openid "; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 09e427dc..e24430da 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,6 +16,7 @@ application: verification-request: ${REDIS_TTL_VERIFICATION_REQUEST:300} verified-session: ${REDIS_TTL_VERIFIED_SESSION:600} credential-metadata: ${REDIS_TTL_ISSUING_FLOW:600} + environment: ${ENVIRONMENT:local} gateway: host: https://${GATEWAY_HOST} organisation-id: ${GATEWAY_ORGANISATION_ID} @@ -41,9 +42,15 @@ application: signature: secret-key: ${SIGNATURE_SECRET_KEY} +com: + amazonaws: + xray: + emitters: + daemon-address: ${AWS_XRAY_DAEMON_ADDRESS:} + sentry: dsn: ${SENTRY_DSN:} - environment: ${SENTRY_ENVIRONMENT:} + environment: ${application.environment} spring: data: diff --git a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java new file mode 100644 index 00000000..32c721db --- /dev/null +++ b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java @@ -0,0 +1,47 @@ +/* + * The MIT License (MIT) + * + * Copyright 2023 Crown Copyright (Health Education England) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package uk.nhs.hee.tis.trainee.credentials.config; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; +import javax.servlet.Filter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AwsXrayConfigurationTest { + + private AwsXrayConfiguration configuration; + + @BeforeEach + void setUp() { + configuration = new AwsXrayConfiguration(); + } + + @Test + void shouldCreateInstanceOfAwsXrayServletFilter() { + Filter filter = configuration.tracingFilter("testEnvironment"); + + assertThat("Unexpected filter type.", filter, instanceOf(AWSXRayServletFilter.class)); + } +} From 3ea6d5333d053f29921e652bcf82302d6f6b186f Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Fri, 24 Mar 2023 16:22:16 +0000 Subject: [PATCH 2/9] fix: add test environment var --- src/test/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index be0b182e..9cc7b910 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -5,5 +5,6 @@ application: verification-request: 60 verified-session: 60 credential-metadata: 60 + environment: local signature: secret-key: test-secret-key From 500026021b41a9cd2fe05d7b8b5f3fe60b25de2e Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Fri, 24 Mar 2023 16:35:53 +0000 Subject: [PATCH 3/9] fix: javadocs --- .../tis/trainee/credentials/config/AwsXrayConfiguration.java | 3 +++ .../hee/tis/trainee/credentials/config/AwsXrayInterceptor.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java index 8020c800..1915b58c 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java @@ -28,6 +28,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Configure the AWS Xray filter with the segment name set to service and environment. + */ @Configuration @ConditionalOnExpression("!T(org.springframework.util.StringUtils)" + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java index ff52001a..c1f0f166 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java @@ -27,6 +27,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +/** + * Extend the Xray interceptor with instructions to wrap annotated Resource and Service beans. + */ @Aspect @Component @ConditionalOnExpression("!T(org.springframework.util.StringUtils)" From 43c1a544f7f375f372b06084e9a3fa2ed3772d95 Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Mon, 27 Mar 2023 14:48:59 +0100 Subject: [PATCH 4/9] fix: readme variable name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 284752b3..b16dd616 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ gradlew bootRun | GATEWAY_ISSUING_REDIRECT_URI | Where the gateway issue should redirect to after issuing. | | | GATEWAY_VERIFICATION_REDIRECT_URI | Where the gateway issue should redirect to verify a supplied credential, e.g. `<host>/api/credentials/verify/callback`. | | | SENTRY_DSN | A Sentry error monitoring Data Source Name. | | -| SENTRY_ENVIRONMENT | The environment to log Sentry events against. | local | +| ENVIRONMENT | The environment to log Sentry events against. | local | | SIGNATURE_SECRET_KEY | The secret key used to validate signed data. | | #### Usage Examples From 150ef7928fe4c74fd2da1755612f473c283ffc9b Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Tue, 28 Mar 2023 11:13:38 +0100 Subject: [PATCH 5/9] fix: build class introspection error --- README.md | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b16dd616..e386e660 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ gradlew bootRun | DB_NAME | The name of the MongoDB database. | credentials | | DB_USER | The username to access the MongoDB instance. | admin | | DB_PASSWORD | The password to access the MongoDB instance. | pwd | +| ENVIRONMENT | The environment to log events against. | local | | GATEWAY_HOST | The credential gateway host. | | | GATEWAY_CLIENT_ID | The client ID for the credential gateway. | | | GATEWAY_CLIENT_SECRET | The client secret for the credential gateway. | | @@ -31,7 +32,6 @@ gradlew bootRun | GATEWAY_ISSUING_REDIRECT_URI | Where the gateway issue should redirect to after issuing. | | | GATEWAY_VERIFICATION_REDIRECT_URI | Where the gateway issue should redirect to verify a supplied credential, e.g. `<host>/api/credentials/verify/callback`. | | | SENTRY_DSN | A Sentry error monitoring Data Source Name. | | -| ENVIRONMENT | The environment to log Sentry events against. | local | | SIGNATURE_SECRET_KEY | The secret key used to validate signed data. | | #### Usage Examples diff --git a/build.gradle b/build.gradle index 5aa3821c..c7cdfb2f 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ dependencies { //AWS X-ray implementation "com.amazonaws:aws-xray-recorder-sdk-spring:2.13.0" - compileOnly "javax.servlet:javax.servlet-api:4.0.1" + implementation "javax.servlet:javax.servlet-api:4.0.1" implementation "commons-codec:commons-codec:1.16.0" From 9ee8a622aeb59f45820a87416864f333feeda4c0 Mon Sep 17 00:00:00 2001 From: Reuben Roberts <reuben.roberts@hee.nhs.uk> Date: Tue, 28 Mar 2023 11:52:54 +0100 Subject: [PATCH 6/9] feat: demo of 'working' trace --- README.md | 8 +--- .../credentials/api/VerifyResource.java | 3 ++ .../config/AwsXrayConfigurationTest.java | 42 ++++++++++++++++--- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e386e660..d5b4eb77 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ gradlew bootRun | Name | Description | Default | |-----------------------------------|-------------------------------------------------------------------------------------------------------------------------|-------------| -| AWS_XRAY_DAEMON_ADDRESS | The AWS XRay daemon host. | | +| AWS_XRAY_DAEMON_ADDRESS | The AWS XRay daemon host. | | | DB_HOST | The MongoDB host to connect to. | localhost | | DB_PORT | The port to connect to MongoDB on. | 27017 | | DB_NAME | The name of the MongoDB database. | credentials | | DB_USER | The username to access the MongoDB instance. | admin | | DB_PASSWORD | The password to access the MongoDB instance. | pwd | -| ENVIRONMENT | The environment to log events against. | local | +| ENVIRONMENT | The environment to log events against. | local | | GATEWAY_HOST | The credential gateway host. | | | GATEWAY_CLIENT_ID | The client ID for the credential gateway. | | | GATEWAY_CLIENT_SECRET | The client secret for the credential gateway. | | @@ -40,14 +40,12 @@ gradlew bootRun The Gradle `test` task can be used to run automated tests and produce coverage reports. - ```shell gradlew test ``` The Gradle `check` lifecycle task can be used to run automated tests and also verify formatting conforms to the code style guidelines. - ```shell gradlew check ``` @@ -59,9 +57,7 @@ gradlew bootBuildImage ``` ## Versioning - This project uses [Semantic Versioning](semver.org). ## License - This project is license under [The MIT License (MIT)](LICENSE). diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java index 672b3852..bb0188bb 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java @@ -21,6 +21,7 @@ package uk.nhs.hee.tis.trainee.credentials.api; +import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.spring.aop.XRayEnabled; import jakarta.validation.Valid; import java.net.URI; @@ -60,10 +61,12 @@ public class VerifyResource { ResponseEntity<String> verifyIdentity(@RequestHeader(HttpHeaders.AUTHORIZATION) String authToken, @RequestParam(required = false) String state, @Valid @RequestBody IdentityDataDto dto) { + AWSXRay.beginSegment("verify-identity"); //TODO figure out how to get rid of this log.info("Received request to start identity verification."); URI uri = service.startIdentityVerification(authToken, dto, state); log.info("Identity verification successfully started."); + AWSXRay.endSegment(); return ResponseEntity.created(uri).body(uri.toString()); } diff --git a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java index 32c721db..45064224 100644 --- a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java +++ b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java @@ -23,25 +23,55 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; -import javax.servlet.Filter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; class AwsXrayConfigurationTest { - private AwsXrayConfiguration configuration; + private static final String DAEMON_PROPERTY = "com.amazonaws.xray.emitters.daemon-address"; + + private ApplicationContextRunner runner; @BeforeEach void setUp() { - configuration = new AwsXrayConfiguration(); + runner = new ApplicationContextRunner() + .withUserConfiguration(AwsXrayConfiguration.class); + } + + @Test + void shouldDisableConfigIfDaemonAddressNotSet() { + runner + .withPropertyValues(DAEMON_PROPERTY + "=") + .run(context -> assertThat("Unexpected bean presence.", + context.containsBean("tracingFilter"), is(false))); } @Test - void shouldCreateInstanceOfAwsXrayServletFilter() { - Filter filter = configuration.tracingFilter("testEnvironment"); + void shouldEnableConfigIfDaemonAddressSet() { + runner + .withPropertyValues(DAEMON_PROPERTY + "=https://localhost:1234") + .run(context -> assertAll( + () -> assertThat("Unexpected bean presence.", + context.containsBean("awsXrayConfiguration"), is(true)), + () -> assertThat("Unexpected bean type.", context.getBean("awsXrayConfiguration"), + instanceOf(AwsXrayConfiguration.class)) + )); + } - assertThat("Unexpected filter type.", filter, instanceOf(AWSXRayServletFilter.class)); + @Test + void shouldRegisterAwsXrayTracingFilterWhenConfigEnabled() { + runner + .withPropertyValues(DAEMON_PROPERTY + "=https://localhost:1234") + .run(context -> assertAll( + () -> assertThat("Unexpected bean presence.", context.containsBean("tracingFilter"), + is(true)), + () -> assertThat("Unexpected bean type.", context.getBean("tracingFilter"), + instanceOf(AWSXRayServletFilter.class)) + )); } } From 67863557b46725a0147e59019025411bcec61632 Mon Sep 17 00:00:00 2001 From: Andy Dingley <Judge40@users.noreply.github.com> Date: Thu, 31 Aug 2023 21:34:40 +0100 Subject: [PATCH 7/9] fix: tracing filter registration Update the AWS Xray SDK to `2.14.0`, which allows it to support Spring 6's Jakarta based Filters. Move the tracing filter configuration to FilterConfiguration as the tracing filter must be placed before the SignedDataFilter in the filter chain. Failure to register them in this order will cause the trace to fail for any unsigned data, as the SignedDataFilter returns an error instead of continuing the filter chain calls. TIS21-4226 --- build.gradle | 4 +- .../config/AwsXrayConfiguration.java | 43 ----------- .../filter/FilterConfiguration.java | 23 ++++++ .../config/AwsXrayConfigurationTest.java | 77 ------------------- .../filter/FilterConfigurationTest.java | 46 +++++++++++ 5 files changed, 70 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java delete mode 100644 src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java diff --git a/build.gradle b/build.gradle index c7cdfb2f..126b94cd 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,7 @@ dependencies { implementation "io.awspring.cloud:spring-cloud-aws-messaging" //AWS X-ray - implementation "com.amazonaws:aws-xray-recorder-sdk-spring:2.13.0" - implementation "javax.servlet:javax.servlet-api:4.0.1" + implementation "com.amazonaws:aws-xray-recorder-sdk-spring:2.14.0" implementation "commons-codec:commons-codec:1.16.0" @@ -65,7 +64,6 @@ dependencies { testImplementation "org.springframework.cloud:spring-cloud-starter-bootstrap" testImplementation "com.playtika.testcontainers:embedded-redis:2.3.2" testImplementation "org.testcontainers:junit-jupiter:1.18.3" - testImplementation "javax.servlet:javax.servlet-api:4.0.1" } dependencyManagement { diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java deleted file mode 100644 index 1915b58c..00000000 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfiguration.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright 2023 Crown Copyright (Health Education England) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package uk.nhs.hee.tis.trainee.credentials.config; - -import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; -import javax.servlet.Filter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configure the AWS Xray filter with the segment name set to service and environment. - */ -@Configuration -@ConditionalOnExpression("!T(org.springframework.util.StringUtils)" - + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") -public class AwsXrayConfiguration { - - @Bean - public Filter tracingFilter(@Value("${application.environment}") String environment) { - return new AWSXRayServletFilter("tis-trainee-credentials-" + environment); - } -} diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfiguration.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfiguration.java index 69c85c54..4eb3b641 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfiguration.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfiguration.java @@ -21,9 +21,13 @@ package uk.nhs.hee.tis.trainee.credentials.filter; +import com.amazonaws.xray.jakarta.servlet.AWSXRayServletFilter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; /** * A configuration for request filters. @@ -31,6 +35,23 @@ @Configuration public class FilterConfiguration { + /** + * Configure the AWS Xray filter with the segment name set to service and environment. + * + * @param environment The environment to use. + * @return The {@link AWSXRayServletFilter} for the registration. + */ + @Bean + @Order(0) + @ConditionalOnExpression("!T(org.springframework.util.StringUtils)" + + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") + public FilterRegistrationBean<AWSXRayServletFilter> registerTracingFilter( + @Value("${application.environment}") String environment) { + FilterRegistrationBean<AWSXRayServletFilter> registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new AWSXRayServletFilter("tis-trainee-credentials-" + environment)); + return registrationBean; + } + /** * Register a {@link SignedDataFilter}. * @@ -38,6 +59,7 @@ public class FilterConfiguration { * @return The {@link FilterRegistrationBean} for the registration. */ @Bean + @Order(1) public FilterRegistrationBean<SignedDataFilter> registerSignedDataFilter( SignedDataFilter filter) { FilterRegistrationBean<SignedDataFilter> registrationBean = new FilterRegistrationBean<>(); @@ -55,6 +77,7 @@ public FilterRegistrationBean<SignedDataFilter> registerSignedDataFilter( * @return The {@link FilterRegistrationBean} for the registration. */ @Bean + @Order(1) public FilterRegistrationBean<VerifiedSessionFilter> registerVerifiedSessionFilter( VerifiedSessionFilter filter) { FilterRegistrationBean<VerifiedSessionFilter> registrationBean = new FilterRegistrationBean<>(); diff --git a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java deleted file mode 100644 index 45064224..00000000 --- a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayConfigurationTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright 2023 Crown Copyright (Health Education England) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package uk.nhs.hee.tis.trainee.credentials.config; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertAll; - -import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -class AwsXrayConfigurationTest { - - private static final String DAEMON_PROPERTY = "com.amazonaws.xray.emitters.daemon-address"; - - private ApplicationContextRunner runner; - - @BeforeEach - void setUp() { - runner = new ApplicationContextRunner() - .withUserConfiguration(AwsXrayConfiguration.class); - } - - @Test - void shouldDisableConfigIfDaemonAddressNotSet() { - runner - .withPropertyValues(DAEMON_PROPERTY + "=") - .run(context -> assertThat("Unexpected bean presence.", - context.containsBean("tracingFilter"), is(false))); - } - - @Test - void shouldEnableConfigIfDaemonAddressSet() { - runner - .withPropertyValues(DAEMON_PROPERTY + "=https://localhost:1234") - .run(context -> assertAll( - () -> assertThat("Unexpected bean presence.", - context.containsBean("awsXrayConfiguration"), is(true)), - () -> assertThat("Unexpected bean type.", context.getBean("awsXrayConfiguration"), - instanceOf(AwsXrayConfiguration.class)) - )); - } - - @Test - void shouldRegisterAwsXrayTracingFilterWhenConfigEnabled() { - runner - .withPropertyValues(DAEMON_PROPERTY + "=https://localhost:1234") - .run(context -> assertAll( - () -> assertThat("Unexpected bean presence.", context.containsBean("tracingFilter"), - is(true)), - () -> assertThat("Unexpected bean type.", context.getBean("tracingFilter"), - instanceOf(AWSXRayServletFilter.class)) - )); - } -} diff --git a/src/test/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfigurationTest.java b/src/test/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfigurationTest.java index 067edf91..c86cfb83 100644 --- a/src/test/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfigurationTest.java +++ b/src/test/java/uk/nhs/hee/tis/trainee/credentials/filter/FilterConfigurationTest.java @@ -22,19 +22,28 @@ package uk.nhs.hee.tis.trainee.credentials.filter; import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.Mockito.mock; +import com.amazonaws.xray.jakarta.servlet.AWSXRayServletFilter; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import uk.nhs.hee.tis.trainee.credentials.service.VerificationService; class FilterConfigurationTest { + private static final String DAEMON_PROPERTY = "com.amazonaws.xray.emitters.daemon-address"; + private static final String SIGNATURE_SECRET_KEY = "test-secret-key"; FilterConfiguration configuration; @@ -44,6 +53,43 @@ void setUp() { configuration = new FilterConfiguration(); } + @Test + void shouldNotRegisterTracingFilterWhenDaemonAddressNotSet() { + ApplicationContextRunner runner = new ApplicationContextRunner() + .withUserConfiguration(FilterConfiguration.class) + .withBean(SignedDataFilter.class, () -> new SignedDataFilter(null, null, null)) + .withBean(VerifiedSessionFilter.class, () -> new VerifiedSessionFilter(null)); + + runner + .withPropertyValues(DAEMON_PROPERTY + "=") + .run(context -> assertThat("Unexpected bean presence.", + context.containsBean("registerTracingFilter"), is(false))); + } + + @Test + void shouldRegisterTracingFilterWhenDaemonAddressSet() { + ApplicationContextRunner runner = new ApplicationContextRunner() + .withUserConfiguration(FilterConfiguration.class) + .withBean(SignedDataFilter.class, () -> new SignedDataFilter(null, null, null)) + .withBean(VerifiedSessionFilter.class, () -> new VerifiedSessionFilter(null)); + + runner + .withPropertyValues(DAEMON_PROPERTY + "=https://localhost:1234") + .run(context -> assertAll( + () -> assertThat("Unexpected bean presence.", + context.containsBean("registerTracingFilter"), is(true)), + () -> assertThat("Unexpected bean type.", context.getBean("registerTracingFilter"), + instanceOf(FilterRegistrationBean.class)) + )); + } + + @Test + void shouldRegisterTracingFilter() { + var registrationBean = configuration.registerTracingFilter("test"); + AWSXRayServletFilter registeredFilter = registrationBean.getFilter(); + assertThat("Unexpected registered filter.", registeredFilter, notNullValue()); + } + @Test void shouldRegisterSignedDataFilter() { SignedDataFilter filter = new SignedDataFilter(new ObjectMapper(), SIGNATURE_SECRET_KEY, null); From c30a3cbb2f7d5d9d32a01c144bcc960b7ae3fe1c Mon Sep 17 00:00:00 2001 From: Andy Dingley <Judge40@users.noreply.github.com> Date: Thu, 31 Aug 2023 21:48:21 +0100 Subject: [PATCH 8/9] feat: add ECS metadata to XRay traces TIS21-4226 --- .../config/AwsXrayInterceptor.java | 27 +++++ .../config/EcsMetadataConfiguration.java | 30 +++++ .../config/AwsXrayInterceptorTest.java | 103 ++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptorTest.java diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java index c1f0f166..bc76dc95 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptor.java @@ -21,11 +21,18 @@ package uk.nhs.hee.tis.trainee.credentials.config; +import com.amazonaws.xray.entities.Subsegment; import com.amazonaws.xray.spring.aop.AbstractXRayInterceptor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import java.util.Optional; +import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata; /** * Extend the Xray interceptor with instructions to wrap annotated Resource and Service beans. @@ -36,6 +43,26 @@ + ".isEmpty('${com.amazonaws.xray.emitters.daemon-address}')") public class AwsXrayInterceptor extends AbstractXRayInterceptor { + private final EcsMetadata ecsMetadata; + private final ObjectMapper mapper; + + AwsXrayInterceptor(Optional<EcsMetadata> ecsMetadata, ObjectMapper mapper) { + this.ecsMetadata = ecsMetadata.orElse(null); + this.mapper = mapper; + } + + @Override + protected Map<String, Map<String, Object>> generateMetadata(ProceedingJoinPoint pjp, + Subsegment subsegment) { + Map<String, Map<String, Object>> metadata = super.generateMetadata(pjp, subsegment); + + Map<String, Object> taskMetadataMap = mapper.convertValue(ecsMetadata, new TypeReference<>() { + }); + metadata.put("EcsMetadata", taskMetadataMap); + + return metadata; + } + @Override @Pointcut( "@within(com.amazonaws.xray.spring.aop.XRayEnabled) && (bean(*Resource) || bean(*Service))") diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/EcsMetadataConfiguration.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/EcsMetadataConfiguration.java index 53c4b5f3..8746aefc 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/EcsMetadataConfiguration.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/config/EcsMetadataConfiguration.java @@ -30,6 +30,9 @@ import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata.ContainerMetadata; import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata.TaskMetadata; +/** + * Configuration for retrieval of ECS metadata. + */ @Configuration @ConditionalOnExpression("!T(org.springframework.util.StringUtils)" + ".isEmpty('${ecs.container.metadata.uri.v4:}')") @@ -53,6 +56,12 @@ public EcsMetadata ecsMetadata(RestTemplate restTemplate, return new EcsMetadata(taskMetadata, containerMetadata); } + /** + * A representation of ECS metadata. + * + * @param taskMetadata The ECS task metadata. + * @param containerMetadata The ECS container metadata. + */ public record EcsMetadata( @JsonProperty("TaskMetadata") TaskMetadata taskMetadata, @@ -60,6 +69,14 @@ public record EcsMetadata( @JsonProperty("ContainerMetadata") ContainerMetadata containerMetadata) { + /** + * A representation of ECS task metadata. + * + * @param cluster The ECS cluster. + * @param taskArn The running task's ARN. + * @param family The task definition family. + * @param revision The task definition revision. + */ record TaskMetadata( @JsonProperty("Cluster") String cluster, @@ -75,6 +92,12 @@ record TaskMetadata( } + /** + * A representation of ECS container metadata. + * + * @param containerArn The ECS container ARN. + * @param logOptions The container's log options. + */ record ContainerMetadata( @JsonProperty("ContainerARN") @@ -83,6 +106,13 @@ record ContainerMetadata( @JsonProperty("LogOptions") LogOptions logOptions) { + /** + * A representation of ECS container log options. + * + * @param logGroup The container's log group. + * @param region The container's log region. + * @param logStream The container's current log stream. + */ record LogOptions( @JsonProperty("awslogs-group") String logGroup, diff --git a/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptorTest.java b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptorTest.java new file mode 100644 index 00000000..31a850b2 --- /dev/null +++ b/src/test/java/uk/nhs/hee/tis/trainee/credentials/config/AwsXrayInterceptorTest.java @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright 2023 Crown Copyright (Health Education England) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package uk.nhs.hee.tis.trainee.credentials.config; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.amazonaws.xray.entities.Subsegment; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import java.util.Optional; +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata; +import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata.ContainerMetadata; +import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata.ContainerMetadata.LogOptions; +import uk.nhs.hee.tis.trainee.credentials.config.EcsMetadataConfiguration.EcsMetadata.TaskMetadata; + +class AwsXrayInterceptorTest { + + private ObjectMapper objectMapper; + private ProceedingJoinPoint pjp; + private Subsegment subsegment; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + subsegment = mock(Subsegment.class); + + pjp = mock(ProceedingJoinPoint.class); + when(pjp.getTarget()).thenReturn(Object.class); + } + + @Test + void shouldGenerateNullEcsMetadataWhenNotAvailable() { + AwsXrayInterceptor interceptor = new AwsXrayInterceptor(Optional.empty(), objectMapper); + + Map<String, Map<String, Object>> metadata = interceptor.generateMetadata(pjp, subsegment); + + assertThat("Unexpected X-Ray metadata.", metadata, notNullValue()); + + Map<String, Object> ecsMetadataMap = metadata.get("EcsMetadata"); + assertThat("Unexpected ECS metadata.", ecsMetadataMap, nullValue()); + } + + @Test + void shouldGenerateEcsMetadataWhenAvailable() { + EcsMetadata ecsMetadata = new EcsMetadata( + new TaskMetadata("cluster", "taskArn", "family", "revision"), + new ContainerMetadata("containerArn", new LogOptions("logGroup", "region", "logStream"))); + AwsXrayInterceptor interceptor = new AwsXrayInterceptor(Optional.of(ecsMetadata), objectMapper); + + Map<String, Map<String, Object>> metadata = interceptor.generateMetadata(pjp, subsegment); + + assertThat("Unexpected X-Ray metadata.", metadata, notNullValue()); + + Map<String, Object> ecsMetadataMap = metadata.get("EcsMetadata"); + assertThat("Unexpected ECS metadata.", ecsMetadataMap, notNullValue()); + + Map<String, Object> taskMetadataMap = (Map<String, Object>) ecsMetadataMap.get("TaskMetadata"); + assertThat("Unexpected task metadata.", taskMetadataMap, notNullValue()); + assertThat("Unexpected cluster.", taskMetadataMap.get("Cluster"), is("cluster")); + assertThat("Unexpected task ARN.", taskMetadataMap.get("TaskARN"), is("taskArn")); + assertThat("Unexpected family.", taskMetadataMap.get("Family"), is("family")); + + Map<String, Object> containerMetadataMap = (Map<String, Object>) ecsMetadataMap.get( + "ContainerMetadata"); + assertThat("Unexpected container metadata.", containerMetadataMap, notNullValue()); + assertThat("Unexpected container ARN.", containerMetadataMap.get("ContainerARN"), + is("containerArn")); + + Map<String, Object> logOptionsMap = (Map<String, Object>) containerMetadataMap.get( + "LogOptions"); + assertThat("Unexpected log options metadata.", logOptionsMap, notNullValue()); + assertThat("Unexpected log group.", logOptionsMap.get("awslogs-group"), is("logGroup")); + assertThat("Unexpected log region.", logOptionsMap.get("awslogs-region"), is("region")); + assertThat("Unexpected log stream.", logOptionsMap.get("awslogs-stream"), is("logStream")); + } +} From 4b25b9f5f6381605e376cb853d4acbac4c140b99 Mon Sep 17 00:00:00 2001 From: Andy Dingley <Judge40@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:19:06 +0100 Subject: [PATCH 9/9] refactor: remove manual XRay segment TIS21-4226 --- .../uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java index bb0188bb..672b3852 100644 --- a/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java +++ b/src/main/java/uk/nhs/hee/tis/trainee/credentials/api/VerifyResource.java @@ -21,7 +21,6 @@ package uk.nhs.hee.tis.trainee.credentials.api; -import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.spring.aop.XRayEnabled; import jakarta.validation.Valid; import java.net.URI; @@ -61,12 +60,10 @@ public class VerifyResource { ResponseEntity<String> verifyIdentity(@RequestHeader(HttpHeaders.AUTHORIZATION) String authToken, @RequestParam(required = false) String state, @Valid @RequestBody IdentityDataDto dto) { - AWSXRay.beginSegment("verify-identity"); //TODO figure out how to get rid of this log.info("Received request to start identity verification."); URI uri = service.startIdentityVerification(authToken, dto, state); log.info("Identity verification successfully started."); - AWSXRay.endSegment(); return ResponseEntity.created(uri).body(uri.toString()); }