Spring Boot (Kotlin) REST API that manages the submission and retrieval of binary and textual data, persisted, with versioning in S3, secured by Cognito JWTs via the OAuth2 Client Credentials flow.
For deployment and infrastructure, see the main readme and cdk. For identity provider details and credential retrieval, see idp.
These instructions are in addition to the general setup.
-
If you want to be able to run/debug the web app locally, you will need to install JAVA (21+). Gradle is used as a build system, and in oder to be able to interact with it directly (i.e. call
gradlew) you also need to set the environment variableJAVA_HOMEto point to your local JAVA installation directory (so Gradle can find the correct JDK):(Note: JAVA_HOME is Gradle's way of locating a JDK when run from the command line. IntelliJ IDE manages its own set of JDKs and points Gradle to the active one automatically -- you never need to set
JAVA_HOMEyourself when working inside IntelliJ GUI.)Windows (PowerShell):
$env:JAVA_HOME="C:\Users\<User-Name>\.jdks\corretto-21.0.8"
Windows (Command Prompt):
set JAVA_HOME=C:\Users\<User-Name>\.jdks\corretto-21.0.8
Linux (apt-based):
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64macOS (use
-vif multiple JDKs installed):export JAVA_HOME=$(/usr/libexec/java_home -v 21)
-
The app interacts with AWS resources (S3, STS) at runtime, whether running locally or deployed. It always needs valid credentials.
Deployed (ECS/EKS): The container uses the IAM task role defined in the CDK infrastructure. Your local credentials (used to deploy) act as an upper boundary -- they control what permissions can be assigned to the IAM role. The IAM role itself is the runtime boundary -- it limits what the running container can actually do.
-
If behind a corporate proxy like Zscaler, you need to import its certificate into the JDK's truststore for the app to download dependencies at build time and access the internet at runtime. See local/proxy readme.md for import instructions.
You may also need to increase Jib timeouts in
gradle.properties(or better,~/.gradle/gradle.propertiessince these are machine-specific):systemProp.jib.httpTimeout=300000 systemProp.jib.connectionTimeout=300000
For proxy issues affecting CDK or Docker, see the root readme.
./gradlew clean build
./gradlew bootRunOnce running:
- Swagger UI: http://localhost:8080/swagger-ui/index.html
- Actuator health: http://localhost:8080/actuator/health
- Actuator info: http://localhost:8080/actuator/info
- Actuator metrics: http://localhost:8080/actuator/metrics
- Prometheus endpoint: http://localhost:8080/actuator/prometheus
-
-
Base Profile (
application.yaml)At build time, Gradle injects the contents of
config_common.yamlintoapplication.yamlby replacing the# configCommon_placeholdercomment. This makes values likeconfigCommon.appVersionandconfigCommon.serviceNameavailable as normal Spring properties.Key runtime properties and where they come from:
Property Source when deployed Source when local app.version${configCommon.appVersion}(injected at build)same app.aws.regionAWS_REGIONenv var (set by CDK on container)AWS_REGIONenv var (set by you)app.s3.bucket-nameS3_DATA_BUCKETenv var (set by CDK per stack - originally from cdk/config.yaml)falls back to dev bucket name in YAML JWT issuer URI SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI(set by CDK)falls back to hardcoded Cognito pool URL in YAML Spring's standard resolution order applies: env var > system property > YAML value. The YAML inline defaults (after
:) exist as a safety net, but you should always haveAWS_PROFILEandAWS_REGIONset locally for consistency with CI.
-
Test Harness (
application-test-harness.yaml)Activating the
test-harnessprofile adds-nosec(unauthenticated) versions of every API endpoint, as well as an additional degug endpoints that provide additional information. Useful for local manual testing (without obtaining tokens).SPRING_PROFILES_ACTIVE=test-harness ./gradlew bootRun
This is independent of the dev/release distinction. Both deployed environments run without test-harness. The profile only controls whether
ConfigLogicNoSecControllerandHelloController(annotated@Profile("test-harness")) are active.
-
All endpoints are under /api/conf-logic/ and require a Bearer JWT (Cognito) with appropriate role claims.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /versions |
Role_Read |
Metadata (version, lastModified) for products and image |
| GET | /products |
Role_Read |
Download json (text) |
| POST | /products |
Role_Write |
Upload json (validated text) |
| GET | /products/image |
Role_Read |
Download image (binary) |
| POST | /products/image |
Role_Write |
Upload image (binary) |
Swagger UI: /swagger-ui/index.html
With test-harness profile active, all endpoints are also available at *-nosec paths without authentication.
Swagger UI is available at /swagger-ui/index.html.
Locally (with test-harness profile active): all endpoints appear twice -- the standard paths (requiring a Bearer token) and the -nosec variants (no auth needed). Use the -nosec paths to test without credentials.
Against the deployed dev instance: all endpoints require authentication. Click the "Authorize" button in Swagger UI and paste a Bearer token. Tokens are short-lived JWTs issued by Cognito.
First export the client credentials (retrieve these from the Cognito stack -- see idp):
export INT_CLIENT_ID=<val>
export INT_CLIENT_SECRET=<val>
export EXT_CLIENT_ID=<val>
export EXT_CLIENT_SECRET=<val>Then fetch a token:
Internal client (read + write):
curl -s -u "$INT_CLIENT_ID:$INT_CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=api://my-backend/read+api://my-backend/write" \
"https://my-backend-auth-739275440763.auth.eu-west-3.amazoncognito.com/oauth2/token" \
| grep -o '"access_token":"[^"]*' | cut -d'"' -f4External client (read only):
curl -s -u "$EXT_CLIENT_ID:$EXT_CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=api://my-backend/read" \
"https://my-backend-auth-739275440763.auth.eu-west-3.amazoncognito.com/oauth2/token" \
| grep -o '"access_token":"[^"]*' | cut -d'"' -f4Copy the printed token string. In Swagger UI click "Authorize", enter Bearer <token>, and confirm. The token is valid for a short window (typically 1 hour); re-run the curl when it expires.
For test tiers, Gradle commands, FakeAws, and the testing architecture, see src/testing.md. For manual end-to-end tests, see src/manualTest/manualTest.md.
Jib builds and pushes container images without needing a local Docker daemon. The target registry is determined by imageSource in config_common.yaml.
ECR (default):
export JIB_USERNAME="AWS"
export JIB_PASSWORD="$(aws ecr get-login-password --region $AWS_REGION)"
./gradlew jibDockerHub:
export JIB_USERNAME="<dockerhub-username>"
export JIB_PASSWORD="<dockerhub-token>"
./gradlew jibThe image path is resolved by resolveJibImagePath() in build.gradle.kts, which reads the AWS account ID (from env or aws sts get-caller-identity), region, and image name from config_common.yaml.
To build a local Docker image instead (requires Docker running):
./gradlew jibDockerBuild