Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
.gradle
.agents
.settings
.vscode
BOOT-INF
WEB-INF
bin
config
docker
gradle
node_modules
build
log
report.html
report-docker.html
2 changes: 1 addition & 1 deletion .github/workflows/makefile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
java-version: '17'

- name: Run Tests for PR
run: make test
run: make test
137 changes: 92 additions & 45 deletions .github/workflows/trivy-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,101 @@ on:
- develop-*
- master

workflow_dispatch: # Allows manual runs from the GitHub UI
workflow_dispatch: # Allows manual runs from the GitHub UI

jobs:
trivy-scan:
runs-on: ubuntu-latest


steps:
# Checkout the pull request code
- name: Checkout code
uses: actions/checkout@v4

# Set up Java 17
- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin' # Use Eclipse Temurin distribution for Java

# Install Trivy if not cached
- name: Install Trivy
run: |
TRIVY_LATEST_VERSION=$(curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | jq -r .tag_name)
wget https://github.com/aquasecurity/trivy/releases/download/${TRIVY_LATEST_VERSION}/trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb
sudo dpkg -i trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb

# Run Trivy to scan the repository or container image
- name: Run Trivy scan
id: trivy-scan
run: |
./gradlew dependencies --write-locks
trivy fs gradle.lockfile --format json --output trivy_report.json --severity HIGH,CRITICAL
/bin/rm -rf gradle/dependency-locks
/bin/rm gradle.lockfile
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db

# Check for high or critical vulnerabilities and output if found
- name: Check and output vulnerabilities
id: check-vulnerabilities
run: |
vuln_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL")] | length' trivy_report.json)
echo "Vulnerability count: $vuln_count"
if [ "$vuln_count" -gt 0 ]; then
echo "High or Critical vulnerabilities found!"
jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL") | "\(.VulnerabilityID): \(.PkgName) - Severity: \(.Severity) - Installed Version: \(.InstalledVersion) - Fixed Version: \(.FixedVersion)"' trivy_report.json
exit 1
else
echo "No high or critical vulnerabilities found."
fi
# Checkout code
- name: Checkout code
uses: actions/checkout@v4

# Set up Java 17
- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

# Generate dependency lock for scanning
- name: Generate dependency lock for scanning
run: ./gradlew dependencies --write-locks

# Install Trivy
- name: Install Trivy
run: |
TRIVY_LATEST_VERSION=$(curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | jq -r .tag_name)
wget https://github.com/aquasecurity/trivy/releases/download/${TRIVY_LATEST_VERSION}/trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb
sudo dpkg -i trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb

# Run Trivy scan
- name: Run Trivy scan
id: trivy-scan
run: trivy fs gradle.lockfile --scanners vuln --severity HIGH,CRITICAL --format json --output trivy_report.json --exit-code 0
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db

# Check and output vulnerabilities
- name: Check and output vulnerabilities
id: check-vulnerabilities
run: |
vuln_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL")] | length' trivy_report.json)
echo "Vulnerability count: $vuln_count"
if [ "$vuln_count" -gt 0 ]; then
echo "High or Critical vulnerabilities found!"
jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL") | "\(.VulnerabilityID): \(.PkgName) - Severity: \(.Severity) - Installed Version: \(.InstalledVersion) - Fixed Version: \(.FixedVersion)"' trivy_report.json
exit 1
else
echo "No high or critical vulnerabilities found."
fi

# Remove generated dependency locks
- name: Remove generated dependency locks
if: always()
run: |
rm -rf gradle/dependency-locks
rm -f gradle.lockfile
image-scan:
runs-on: ubuntu-latest

steps:
# Checkout code
- name: Checkout code
uses: actions/checkout@v4


# Build application image
- name: Build application image
run: make docker DOCKER_IMAGE=evsrestapi:ci

# Install Trivy
- name: Install Trivy
run: |
TRIVY_LATEST_VERSION=$(curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | jq -r .tag_name)
wget https://github.com/aquasecurity/trivy/releases/download/${TRIVY_LATEST_VERSION}/trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb
sudo dpkg -i trivy_${TRIVY_LATEST_VERSION#v}_Linux-64bit.deb

# Scan application image
- name: Scan application image
id: image-trivy-scan
run: trivy image evsrestapi:ci --scanners vuln --severity HIGH,CRITICAL --format json --output trivy_image_report.json --exit-code 0
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db

# Check and output image vulnerabilities
- name: Check and output image vulnerabilities
id: check-image-vulnerabilities
run: |
vuln_count=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL")] | length' trivy_image_report.json)
echo "Image vulnerability count: $vuln_count"
if [ "$vuln_count" -gt 0 ]; then
echo "High or Critical image vulnerabilities found!"
jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH" or .Severity == "CRITICAL") | "\(.VulnerabilityID): \(.PkgName) - Severity: \(.Severity) - Installed Version: \(.InstalledVersion) - Fixed Version: \(.FixedVersion)"' trivy_image_report.json
exit 1
else
echo "No high or critical image vulnerabilities found."
fi
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ UnitTestData/*
src/main/main.iml
src/test/test.iml
report-docker.html
.docker-secrets/
/.docker-secrets/
44 changes: 16 additions & 28 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
# Base image
FROM centos:7
MAINTAINER Frankie Parks <frankie.parks@bioappdev.org>
# Build the deployable WAR in an isolated Linux Gradle environment.
FROM gradle:8.14.2-jdk17 AS gradle-build

ENV TZ=America/New_York
RUN ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime && echo "$TZ" > /etc/timezone
WORKDIR /workspace

# Update all packages installed for security
RUN yum makecache && yum update -y
COPY --chown=gradle:gradle build.gradle gradle.properties ./
RUN gradle --no-daemon dependencies

# Create a user and group used to launch processes
# The user ID 1000 is the default for the first "regular" user on Fedora/RHEL,
# so there is a high chance that this ID will be equal to the current user
# making it easier to use volumes (no permission issues)
RUN groupadd -r evsapi -g 1000 && useradd -u 1000 -r -g evsapi -m -d /opt/evsapi -s /sbin/nologin -c "EVSAPI user" evsapi && \
chmod 755 /opt/evsapi
COPY --chown=gradle:gradle src/main ./src/main
RUN gradle --no-daemon bootWar -x test

# Set the working directory to evsapi' user home directory
WORKDIR /opt/evsapi
# Run only the packaged application as an unprivileged user.
FROM eclipse-temurin:17-jre-jammy

# Install necessary packages
RUN yum -y install java-1.8.0-openjdk-devel unzip && yum clean all
WORKDIR /app

# Set ENV variable for EVS_SERVER_PORT
ENV EVS_SERVER_PORT="5830"
ENV JAVA_OPTIONS="-Xmx2048m -XX:PermSize=1024m -XX:MaxPermSize=512m"
RUN groupadd --system evsapi \
&& useradd --system --gid evsapi --home-dir /app --shell /usr/sbin/nologin evsapi

# Add file files to image
ADD build/distributions/evsrestapi-1.0.0-SNAPSHOT.zip /opt/evsapi/
RUN unzip evsrestapi-1.0.0-SNAPSHOT.zip
RUN ln -s evsrestapi-1.0.0-SNAPSHOT evsrestapi
COPY --from=gradle-build --chown=evsapi:evsapi /workspace/build/libs/evsrestapi-*.war /app/evsrestapi.war

RUN ls -l

EXPOSE 5830
USER evsapi
CMD java -jar ./evsrestapi/lib/evsrestapi.war

EXPOSE 8082

ENTRYPOINT ["java", "-jar", "/app/evsrestapi.war"]
92 changes: 83 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,95 @@ GIT_COMMIT ?= $(shell echo `git log | grep -m1 -oE '[^ ]+$'`)
GIT_COMMITTED_AT ?= $(shell echo `git log -1 --format=%ct`)
GIT_BRANCH ?=
FULL_VERSION := v$(APP_VERSION)-g$(GIT_VERSION)
DOCKER_TAG := $(shell grep "^version =" build.gradle | sed 's/version = //; s/"//g; s/.RELEASE//')
DOCKER_IMAGE ?= $(SERVICE):$(DOCKER_TAG)
ES_PORT ?= 9201
ES_SCHEME ?= http
GRAPH_DB_PORT ?= 3030
GRAPH_DB ?= NCIT2
DOCKER_PORT ?= 8082
DOCKER_ES_HOST ?= host.docker.internal
DOCKER_GRAPH_DB_HOST ?= host.docker.internal
DOCKER_SECRETS_DIR ?= $(CURDIR)/.docker-secrets
DOCKER_IMAGE_STAMP := build/.docker-image-$(subst :,_,$(subst /,_,$(DOCKER_IMAGE)))
DOCKER_BUILD_INPUTS := Dockerfile .dockerignore build.gradle gradle.properties $(shell git ls-files --cached --others --exclude-standard src/main)

.PHONY: build
GRADLEW ?= ./gradlew

ifeq ($(OS),Windows_NT)
DOCKER ?= docker.exe
DOCKER_HOST_GATEWAY_ARG :=
else
DOCKER ?= docker
DOCKER_HOST_GATEWAY_ARG := --add-host host.docker.internal:host-gateway
endif

.PHONY: build docker dockerpush scandocker rundocker check-docker-secrets scan

# consider also "docker save..." and "docker load..." to avoid registry.
clean:
./gradlew clean
$(GRADLEW) clean

# Build the library without tests
# On Windows use: git config core.eol lf
build:
./gradlew clean spotlessApply build spotbugsMain spotbugsTest -x test -x zipFile
$(GRADLEW) clean spotlessApply build spotbugsMain spotbugsTest -x test -x zipFile

run: build
java -Dspring.profiles.active=local -jar build/libs/evsrestapi*.war

# Build the application and image in an isolated Linux/AMD64 Docker build environment.
docker: $(DOCKER_IMAGE_STAMP)
@$(DOCKER) image inspect "$(DOCKER_IMAGE)" > /dev/null 2>&1 || { rm -f "$(DOCKER_IMAGE_STAMP)"; $(MAKE) --no-print-directory "$(DOCKER_IMAGE_STAMP)"; }
@echo "Docker image $(DOCKER_IMAGE) is up to date."

$(DOCKER_IMAGE_STAMP): $(DOCKER_BUILD_INPUTS)
$(DOCKER) build --platform linux/amd64 --tag "$(DOCKER_IMAGE)" .
@mkdir -p "$(dir $@)"
@touch "$@"

# Build and push a Linux/AMD64 image. Override DOCKER_IMAGE with a registry-qualified image name.
dockerpush:
$(DOCKER) buildx build --platform linux/amd64 --tag "$(DOCKER_IMAGE)" --push .

# Report all HIGH and CRITICAL image vulnerabilities with their installed and fixed versions.
# The complete HTML report is written to report-docker.html.
scandocker: docker
trivy image "$(DOCKER_IMAGE)" --scanners vuln --severity HIGH,CRITICAL --format table
trivy image "$(DOCKER_IMAGE)" --scanners vuln --format template -o report-docker.html --template "@config/trivy/html.tpl"



# Require a local, ignored directory of Spring Boot config-tree secret files.
check-docker-secrets:
@test -d "$(DOCKER_SECRETS_DIR)" || (echo "ERROR: Create $(DOCKER_SECRETS_DIR) and add secret files before running rundocker." && exit 1)

# Run against Jena/Fuseki and OpenSearch services exposed on the Docker host.
# Secrets are mounted read-only and imported from /run/secrets rather than passed as environment variables.
# Override DOCKER_ES_HOST, DOCKER_GRAPH_DB_HOST, ports, or the secrets directory as needed.
rundocker: docker check-docker-secrets
$(DOCKER) run --rm --name "$(SERVICE)" -p "$(DOCKER_PORT):8082" $(DOCKER_HOST_GATEWAY_ARG) \
--mount type=bind,src="$(DOCKER_SECRETS_DIR)",dst=/run/secrets,readonly \
-e SPRING_CONFIG_IMPORT=optional:configtree:/run/secrets/ \
-e SPRING_PROFILES_ACTIVE=local \
-e EVS_SERVER_PORT=8082 \
-e ES_HOST="$(DOCKER_ES_HOST)" \
-e ES_PORT="$(ES_PORT)" \
-e ES_SCHEME="$(ES_SCHEME)" \
-e GRAPH_DB_HOST="$(DOCKER_GRAPH_DB_HOST)" \
-e GRAPH_DB_PORT="$(GRAPH_DB_PORT)" \
-e GRAPH_DB="$(GRAPH_DB)" \
-e CONFIG_BASE_URI \
-e MAIL_HOST \
-e MAIL_PORT \
-e MAIL_AUTH \
-e MAIL_TLS \
-e MAIL_RECIPIENT \
-e RECAPTCHA_KEY \
"$(DOCKER_IMAGE)"

test:
./gradlew spotlessCheck -x test
$(GRADLEW) spotlessCheck -x test

releasetag:
git tag -a "${VERSION}-RC-`/bin/date +%Y-%m-%d`" -m "Release ${VERSION}-RC-`/bin/date +%Y-%m-%d`"
Expand All @@ -54,9 +126,11 @@ version:
devreset: build
./src/main/bin/devreset.sh ../data/UnitTestData > log 2>&1 &

# Report all HIGH and CRITICAL dependency vulnerabilities with installed and fixed versions.
# The complete HTML report is written to report.html. Generated dependency locks are always removed.
scan:
./gradlew dependencies --write-locks
trivy fs gradle.lockfile --format template -o report.html --template "@config/trivy/html.tpl"
grep CRITICAL report.html
/bin/rm -rf gradle/dependency-locks
/bin/rm gradle.lockfile
@set -e; \
trap 'rm -rf gradle/dependency-locks gradle.lockfile' EXIT; \
$(GRADLEW) dependencies --write-locks; \
trivy fs gradle.lockfile --scanners vuln --severity HIGH,CRITICAL --format table; \
trivy fs gradle.lockfile --scanners vuln --format template -o report.html --template "@config/trivy/html.tpl"
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,21 @@ Information on the build and deployment process for the EVSRESTAPI project

### Run application from command line
* Run with `java -Xmx4096 -Dspring.profiles.active=local -jar build/libs/evsrestapi*.jar`

### Build, scan, and run the application image

* `make docker` builds the WAR inside a Linux/AMD64 Docker build stage and creates `evsrestapi:<version>` without using a local Gradle installation.
* The image starts the executable WAR, which runs the REST API entry point; the executable JAR is reserved for loader and reindex operations.
* `make scandocker` and `make scan` print HIGH/CRITICAL vulnerability tables and write full HTML reports. GitHub Actions enforces HIGH/CRITICAL findings in CI.
* `make dockerpush DOCKER_IMAGE=<registry>/<image>:<tag>` builds and pushes a Linux/AMD64 image with Docker Buildx.
* `make rundocker` runs the image on port 8082 using the `local` Spring profile. It assumes Jena/Fuseki and OpenSearch are already running on the host, uses `host.docker.internal` on Docker Desktop, and adds the host-gateway mapping automatically on Linux.
* `make rundocker` is the supported way to start the image locally. A direct `docker run` must supply equivalent Spring profile, port, service-host, and secret configuration; otherwise the image uses the default application settings rather than the local setup.
* Before running it, create the ignored `.docker-secrets` directory. Each file is mounted read-only at `/run/secrets` and is imported by Spring Boot using its filename as the property name. Put credentials and secrets in `NCI_EVS_ADMIN_KEY`, `MAIL_USER`, `MAIL_PASSWORD`, and `RECAPTCHA_SECRET`; write each value without a trailing newline. Non-sensitive settings such as `MAIL_HOST`, `MAIL_PORT`, and `RECAPTCHA_KEY` continue to be forwarded from the host environment.
* The secret values are not passed as container environment variables, so they do not appear in `docker inspect`. Docker daemon administrators can still access a running container and must remain trusted.
* Override the service hosts or published port when necessary, for example:

```bash
make rundocker DOCKER_ES_HOST=host.docker.internal DOCKER_GRAPH_DB_HOST=host.docker.internal DOCKER_PORT=8082
```

The existing `ES_PORT`, `ES_SCHEME`, `GRAPH_DB_PORT`, and `GRAPH_DB` settings are passed through to the container. Email, reCAPTCHA, and other applicable local configuration environment variables are also forwarded.
8 changes: 7 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ dependencies {

// Use version 2 with spring boot 3
// custom location => springdoc.swagger-ui.path=/swagger-ui/index.html
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.17"

implementation "org.springframework.boot:spring-boot-devtools"
implementation "org.springframework.boot:spring-boot-starter-actuator"
Expand Down Expand Up @@ -142,6 +142,12 @@ dependencies {
//Java Mail Sender dependency
implementation "org.springframework.boot:spring-boot-starter-mail"

// Security override: CVE-2026-41695
implementation "org.springframework.data:spring-data-commons:3.5.12"

// Security override: CVE-2026-41006 and CVE-2026-41007.
implementation "org.springframework.hateoas:spring-hateoas:2.5.3"

// aspectjweaver is now managed by Spring Boot BOM (1.9.25.1 in 3.5.x)
implementation "org.aspectj:aspectjweaver"
implementation "org.apache.commons:commons-text:1.10.0"
Expand Down
Loading