Skip to content
Open
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Agent Instructions

## Available skills

- `docs/skills/security/SKILL.md`
Use for general Spring Security, IDAM/OIDC, and related regression-test work in this repo. For JWT issuer-validation work, use `docs/skills/security-jwt-issuer/SKILL.md`.
Prompt cue: `Use docs/skills/security/SKILL.md`

- `docs/skills/security-jwt-issuer/SKILL.md`
Use for JWT issuer validation, OIDC discovery versus enforced issuer configuration, Helm or Jenkins `OIDC_ISSUER` settings, and related regression-test work in this repo.
Prompt cue: `Use docs/skills/security-jwt-issuer/SKILL.md`
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ARG JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"
ARG APP_INSIGHTS_AGENT_VERSION=3.7.3
ARG PLATFORM=""

FROM hmctspublic.azurecr.io/base/java${PLATFORM}:21-distroless
FROM hmctsprod.azurecr.io/base/java${PLATFORM}:21-distroless

# Change to non-root privilege
USER hmcts
Expand All @@ -15,4 +15,4 @@ COPY lib/applicationinsights.json /opt/app

EXPOSE 4455

CMD ["ccd-case-document-am-api.jar"]
CMD ["ccd-case-document-am-api.jar"]
6 changes: 4 additions & 2 deletions Jenkinsfile_CNP
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ static LinkedHashMap<String, Object> secret(String secretName, String envVar) {
env.CCD_DATA_STORE_API_BASE_URL = "https://ccd-data-store-api-${dataStoreApiDevelopPr}.preview.platform.hmcts.net".toLowerCase()
env.DEFINITION_STORE_URL_BASE = "https://ccd-definition-store-api-${definitionStoreDevelopPr}.preview.platform.hmcts.net".toLowerCase()
env.IDAM_URL = "https://idam-api.aat.platform.hmcts.net"
env.OIDC_ISSUER = "https://forgerock-am.service.core-compute-idam-aat2.internal:8443/openam/oauth2/realms/root/realms/hmcts"
env.S2S_URL = "http://rpe-service-auth-provider-aat.service.core-compute-aat.internal"
env.OAUTH2_CLIENT_ID = "ccd_gateway"
env.OAUTH2_REDIRECT_URI = "https://www-ccd.aat.platform.hmcts.net/oauth2redirect"
Expand All @@ -72,6 +73,7 @@ env.BEFTA_S2S_CLIENT_ID_OF_XUI_WEBAPP = "xui_webapp"
env.BEFTA_S2S_CLIENT_ID_OF_BULK_SCAN_PROCESSOR = "bulk_scan_processor"

env.BEFTA_RESPONSE_HEADER_CHECK_POLICY = "JUST_WARN"
env.VERIFY_OIDC_ISSUER = "true"

env.CCD_API_GATEWAY_S2S_ID = "ccd_gw"

Expand All @@ -82,8 +84,8 @@ env.PACT_BROKER_URL = "pact-broker.platform.hmcts.net"
env.PACT_BROKER_PORT = "443"
env.PACT_BROKER_SCHEME = "https"

// Prevent Docker hub rate limit errors by ensuring that testcontainers uses images from hmctspublic ACR
env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "hmctspublic.azurecr.io/imported/"
// Prevent Docker hub rate limit errors by ensuring that testcontainers uses images from hmctsprod ACR
env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "hmctsprod.azurecr.io/imported/"

withPipeline("java", product, component) {
onMaster {
Expand Down
6 changes: 4 additions & 2 deletions Jenkinsfile_nightly
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ def type = "java"
def product = "ccd"
def component = "case-document-am-api"

// Prevent Docker hub rate limit errors by ensuring that testcontainers uses images from hmctspublic ACR
env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "hmctspublic.azurecr.io/imported/"
// Prevent Docker hub rate limit errors by ensuring that testcontainers uses images from hmctsprod ACR
env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "hmctsprod.azurecr.io/imported/"
env.OIDC_ISSUER = "https://forgerock-am.service.core-compute-idam-aat2.internal:8443/openam/oauth2/realms/root/realms/hmcts"
env.VERIFY_OIDC_ISSUER = "true"

withNightlyPipeline(type, product, component) {
enableSlackNotifications('#ccd-case-document-am-api-builds')
Expand Down
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,34 @@ Users & services with sufficient permissions only will be able to upload, modify

This service works with the DocStore Api and CaseData Api alongside their databases CCD Data Store and Document Management Store.

### Codex Workflow Docs

Repo-local workflow docs are indexed in `AGENTS.md`.

#### Environment variables
The following environment variables are required:

| Name | Default | Description |
|------|---------|-------------|
|CASE_DOCUMENT_S2S_AUTHORISED_SERVICES| ccd_case_document_am_api, ccd_gw, xui_webapp, ccd_data, bulk_scan_processor, bulk_scan_orchestrator|
|REFORM_SERVICE_NAME| ccd-case-document-am-api|
|REFORM_TEAM| ccd
|REFORM_ENVIRONMENT| local
|S2S_SECRET|
|S2S_KEY| S2S_KEY
|CCD_DOCUMENT_API_IDAM_KEY|
|DEFINITION_STORE_HOST|
|USER_PROFILE_HOST|
|DM_STORE_BASE_URL| http://dm-store:8080|
|CCD_DATA_STORE_API_BASE_URL| http://ccd-data-store-api:4452|
|app-insights-connection-string|
|IDAM_USER_URL| http://idam-api:5000 |
|IDAM_S2S_URL| http://service-auth-provider-api:8080|
|JAVA_TOOL_OPTIONS| -XX:InitialRAMPercentage=30.0 -XX:MaxRAMPercentage=65.0 -XX:MinRAMPercentage=30.0 -XX:+UseConcMarkSweepGC -agentlib:jdwp=transport=dt_socket, server=y,suspend=n,address=5005
| CASE_DOCUMENT_S2S_AUTHORISED_SERVICES | ccd_case_document_am_api, ccd_gw, xui_webapp, ccd_data, bulk_scan_processor, bulk_scan_orchestrator | Authorised service names for S2S calls. |
| REFORM_SERVICE_NAME | ccd-case-document-am-api | Service name. |
| REFORM_TEAM | ccd | Owning team. |
| REFORM_ENVIRONMENT | local | Runtime environment name. |
| S2S_SECRET | - | Service-to-service secret. |
| S2S_KEY | S2S_KEY | S2S key alias. |
| CCD_DOCUMENT_API_IDAM_KEY | - | IDAM key for this service where required by local setup. |
| DEFINITION_STORE_HOST | - | Base URL for the definition store dependency. |
| USER_PROFILE_HOST | - | Base URL for the user profile dependency. |
| DM_STORE_BASE_URL | http://dm-store:8080 | Base URL for Document Management Store. |
| CCD_DATA_STORE_API_BASE_URL | http://ccd-data-store-api:4452 | Base URL for CCD Data Store API. |
| app-insights-connection-string | - | Application Insights connection string. |
| IDAM_USER_URL | http://idam-api:5000 | Base URL for IDAM user APIs. |
| IDAM_S2S_URL | http://service-auth-provider-api:8080 | Base URL for S2S auth provider. |
| IDAM_OIDC_URL | - | Base URL for IDAM OIDC discovery and JWKS lookup. |
| OIDC_ISSUER | - | Enforced issuer. This must match the `iss` claim in real access tokens accepted by this service. |
| JAVA_TOOL_OPTIONS | -XX:InitialRAMPercentage=30.0 -XX:MaxRAMPercentage=65.0 -XX:MinRAMPercentage=30.0 -XX:+UseConcMarkSweepGC -agentlib:jdwp=transport=dt_socket, server=y,suspend=n,address=5005 | JVM options for local running. |

`IDAM_OIDC_URL` and `OIDC_ISSUER` are intentionally separate. `IDAM_OIDC_URL` supplies `issuer-uri` for discovery and JWKS retrieval, while `OIDC_ISSUER` supplies the enforced issuer. If `OIDC_ISSUER` does not match the `iss` used in real caller tokens, authenticated requests will be rejected with `401`.

## Building the application

Expand Down Expand Up @@ -154,6 +162,8 @@ export BEFTA_S2S_CLIENT_SECRET_OF_XUI_WEBAPP=AAAAAAAAAAAAAAAA
export DM_STORE_BASE_URL=http://localhost:4506
```

To verify the live enforced issuer locally, export `VERIFY_OIDC_ISSUER=true` together with the normal functional test credentials and `OIDC_ISSUER`. The verifier will fetch a real IDAM token, decode its `iss` claim, and fail if it does not exactly match `OIDC_ISSUER`.

These tests also rely on the `CCD_BEFTA_JURISDICTION2.xlsx` file to be already imported. This file should be available in your local environment already.

####Running the tests
Expand Down
6 changes: 3 additions & 3 deletions acb.tpl.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 1.0-preview-1
steps:
- id: pull-base-image-amd64
cmd: docker pull --platform linux/amd64 hmctspublic.azurecr.io/base/java:21-distroless && docker tag hmctspublic.azurecr.io/base/java:21-distroless hmctspublic.azurecr.io/base/java/linux/amd64:21-distroless
cmd: docker pull --platform linux/amd64 hmctsprod.azurecr.io/base/java:21-distroless && docker tag hmctsprod.azurecr.io/base/java:21-distroless hmctsprod.azurecr.io/base/java/linux/amd64:21-distroless
when: ["-"]
retries: 3
retryDelay: 5
Expand All @@ -18,7 +18,7 @@ steps:
retryDelay: 5

- id: pull-base-image-arm64
cmd: docker pull --platform linux/arm64 hmctspublic.azurecr.io/base/java:21-distroless && docker tag hmctspublic.azurecr.io/base/java:21-distroless hmctspublic.azurecr.io/base/java/linux/arm64:21-distroless
cmd: docker pull --platform linux/arm64 hmctsprod.azurecr.io/base/java:21-distroless && docker tag hmctsprod.azurecr.io/base/java:21-distroless hmctsprod.azurecr.io/base/java/linux/arm64:21-distroless
when:
- pull-base-image-amd64
retries: 3
Expand Down Expand Up @@ -57,4 +57,4 @@ steps:
when:
- manifest-create
retries: 3
retryDelay: 5
retryDelay: 5
47 changes: 46 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ tasks.withType(JavaCompile) {
}

task functional(type: Test, description: 'Runs the functional tests.', group: 'Verification') {
dependsOn 'functionalTestClasses', 'verifyFunctionalTestJwtIssuer'
testClassesDirs = sourceSets.functionalTest.output.classesDirs
classpath = sourceSets.functionalTest.runtimeClasspath

Expand Down Expand Up @@ -165,6 +166,7 @@ task integration(type: Test, description: 'Runs the integration tests.', group:
}

task smoke(type: Test, description: 'Runs the smoke tests.', group: 'Verification') {
dependsOn 'functionalTestClasses', 'verifyFunctionalTestJwtIssuer'
setTestClassesDirs(sourceSets.functionalTest.output.classesDirs)
setClasspath(sourceSets.functionalTest.runtimeClasspath)
include "uk/gov/hmcts/ccd/documentam/befta/**"
Expand Down Expand Up @@ -197,6 +199,48 @@ task smoke(type: Test, description: 'Runs the smoke tests.', group: 'Verificatio
outputs.upToDateWhen { false }
}

task verifyFunctionalTestJwtIssuer(type: JavaExec) {
description = 'Verifies the functional/smoke test token iss matches OIDC_ISSUER'
group = 'Verification'
dependsOn functionalTestClasses

onlyIf {
System.getenv('VERIFY_OIDC_ISSUER')?.toBoolean()
}

mainClass = "uk.gov.hmcts.ccd.documentam.befta.JwtIssuerVerificationApp"
classpath += configurations.cucumberRuntime + sourceSets.functionalTest.runtimeClasspath + sourceSets.main.output + sourceSets.test.output
}

task verifyOidcIssuerPolicy {
description = 'Fails if oidc.issuer is derived from discovery configuration'
group = 'Verification'

doLast {
def policyFiles = [
'src/main/resources/application.yaml',
'src/integrationTest/resources/application-itest.yaml'
]

def violations = policyFiles.findAll { path ->
def configFile = file(path)
configFile.exists() && configFile.readLines().any { line ->
def normalized = line.replaceAll(/\s+/, '')
normalized.startsWith('issuer:${OIDC_ISSUER:${IDAM_OIDC_URL')
|| normalized.startsWith('issuer:${OIDC_ISSUER:${idam.api.url')
}
}

if (!violations.isEmpty()) {
throw new GradleException(
"OIDC issuer policy violation. Do not derive oidc.issuer from discovery config in: ${violations.join(', ')}"
)
}
}
}

check.dependsOn verifyOidcIssuerPolicy

jacocoTestReport {
executionData(test, integration)
reports {
Expand Down Expand Up @@ -297,8 +341,9 @@ dependencies {

// HMCTS Dependencies
implementation group: 'com.github.hmcts', name: 'idam-java-client', version: '3.0.5'

implementation group: 'com.github.hmcts', name: 'service-auth-provider-java-client', version: '5.3.3'
implementation group: 'com.github.hmcts.java-logging', name: 'logging', version: '6.1.9'
implementation group: 'com.github.hmcts.java-logging', name: 'logging', version: '8.0.0'


implementation group: 'com.auth0', name: 'java-jwt', version: '4.5.0'
Expand Down
4 changes: 2 additions & 2 deletions charts/ccd-case-document-am-api/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ appVersion: "1.0"
description: A Helm chart for CCD Case Document AM API
name: ccd-case-document-am-api
home: https://github.com/hmcts/ccd-case-document-am-api
version: 1.7.17
version: 1.7.18
maintainers:
- name: CCD Team
dependencies:
- name: java
version: 5.3.0
repository: 'oci://hmctspublic.azurecr.io/helm'
repository: 'oci://hmctsprod.azurecr.io/helm'
4 changes: 2 additions & 2 deletions charts/ccd-case-document-am-api/values.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
java:
image: 'hmctspublic.azurecr.io/ccd/case-document-am-api:latest'
image: 'hmctsprod.azurecr.io/ccd/case-document-am-api:latest'
ingressHost: ccd-case-document-am-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal
applicationPort: 4455
aadIdentityName: ccd
Expand All @@ -13,7 +13,7 @@ java:
environment:
IDAM_API_URL: https://idam-api.{{ .Values.global.environment }}.platform.hmcts.net
IDAM_OIDC_URL: https://idam-web-public.{{ .Values.global.environment }}.platform.hmcts.net
OIDC_ISSUER: https://forgerock-am.service.core-compute-idam-{{ .Values.global.environment }}.internal:8443/openam/oauth2/hmcts
OIDC_ISSUER: https://forgerock-am.service.core-compute-idam-aat2.internal:8443/openam/oauth2/realms/root/realms/hmcts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are replacing {{ .Values.global.environment }} with aat2 here. Is this correct for all other environments given it is already being overridden in preview.template.yaml ?

S2S_URL: http://rpe-service-auth-provider-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal
CASE_DOCUMENT_S2S_AUTHORISED_SERVICES: ccd_case_document_am_api,ccd_gw,xui_webapp,ccd_data,bulk_scan_processor,sscs,probate_backend,iac,em_npa_app,fprl_dgs_api,dg_docassembly_api,em_stitching_api,em_ccd_orchestrator,cmc_claim_store,civil_service,bulk_scan_orchestrator,ethos_repl_service,divorce_document_generator,finrem_document_generator,finrem_case_orchestration,fpl_case_service,et_cos,prl_cos_api,prl_dgs_api,et_sya_api,adoption_cos_api,adoption_web,nfdiv_case_api,divorce_frontend,sptribs_case_api,sptribs_dss_backend,civil_general_applications,pcs_api
DM_STORE_BASE_URL: http://dm-store-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal
Expand Down
Loading