A comprehensive hands-on workshop for implementing DevSecOps practices with Java, Quarkus, and cloud-native technologies
| # | SHA | Message |
|---|---|---|
| 1 | 30dbf41 | Adding github codespace definition (*initial commit*) |
| 2 | 7b182ba | Adding github codespace definition |
| 3 | b64377c | Adding quarkus skeleton |
| 4 | b3848b0 | Integration testing sample |
| 5 | 3a1bf03 | Downgrading to Java 21 |
| 6 | 872235d | Spotbugs detection demo |
| 7 | d959814 | Making spotbugs happy |
| 8 | ef407fe | Adding IP resource for docker testing |
| 9 | 1258360 | Adding gitignore to hide sensitive data |
| 10 | f4800e7 | Adding basic java action builder |
| 11 | 23f7471 | Adding Eclipse JKube support |
| 12 | 2b4a793 | Adding shift-left analysis |
- Overview
- Prerequisites
- Step 1: Fork the Repository
- Step 2: Let's Get Started!
- Step 3: Creating Quarkus Application Skeleton
- Step 4: Adding Integration Testing
- Step 5: Implementing Static Code Analysis with SpotBugs
- Step 6: Fixing Code Quality Issues
- Step 7: Adding IP Resource for Container Testing
- Step 8: Setting up CI/CD with GitHub Actions
- Step 9: Enabling Kubernetes Deployment with Eclipse JKube (Only for local testing)
- Step 10: Implementing Shift-Left Security with Trivy
- Conclusion
- GitHub account
- Basic knowledge of Java, Maven, and Docker
- Understanding of REST APIs
- Familiarity with Git
That's it, just fork it to your own GitHub account by clicking the "Fork" button at the top right of the repository page.
You can clone or open this project directly in GitHub Codespaces
Then, inside your repository, navigate to the commit 7b182ba
git reset --hard 7b182baYou can always reset the repository with
git reset --hard <commit-sha>Install quarkus CLI if you don't have it yet:
curl -Ls https://sh.quarkus.io | bashYou can install it via SDKMAN as well (SDKMAN is pre-installed in GitHub Codespaces):
sdk install quarkus- Goal: Bootstrap a complete Quarkus REST application with health checks and monitoring
- Commit Reference: b64377c
You can generate this using code.quarkus.io or the Quarkus CLI.
quarkus create app com.vorozco:quarkus-cloud-native-workload:1.0.0-SNAPSHOT \
--extensions=quarkus-rest,quarkus-arc,quarkus-rest-jackson,quarkus-smallrye-health,quarkus-micrometer-registry-prometheus,junit5The key files are:
quarkus-cloud-native-workload/
├── .dockerignore
├── .gitignore
├── .mvn/
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
├── src/
│ ├── main/
│ │ ├── docker/
│ │ │ ├── Dockerfile.jvm
│ │ │ ├── Dockerfile.legacy-jar
│ │ │ ├── Dockerfile.native
│ │ │ └── Dockerfile.native-micro
│ │ ├── java/com/vorozco/
│ │ │ ├── GreetingResource.java
│ │ │ └── MyLivenessCheck.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/java/com/vorozco/
│ ├── GreetingResourceIT.java
│ └── GreetingResourceTest.java
<properties>
<maven.compiler.release>21</maven.compiler.release>
<quarkus.platform.version>3.30.3</quarkus.platform.version>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>src/main/java/com/vorozco/GreetingResource.java:
package com.vorozco;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}src/main/java/com/vorozco/MyLivenessCheck.java:
package com.vorozco;
import org.eclipse.microprofile.health. HealthCheck;
import org.eclipse.microprofile.health. HealthCheckResponse;
import org.eclipse.microprofile.health. Liveness;
@Liveness
public class MyLivenessCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.up("alive");
}
}src/test/java/com/vorozco/GreetingResourceTest.java:
package com.vorozco;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured. RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
class GreetingResourceTest {
@Test
void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("Hello from Quarkus REST"));
}
}cd quarkus-cloud-native-workload
./mvnw quarkus:devAccess the dev UI at http://localhost:8080/q/dev/ (on localhost)
Or access the endpoints directly:
curl http://localhost:8080/hello
# Returns: Hello from Quarkus REST
curl http://localhost:8080/q/health/live
# Returns: {"status":"UP","checks":[{"name":"alive","status":"UP"}]}
curl http://localhost:8080/q/metrics
# Returns: Prometheus metricsWhat you've built:
- RESTful web service with JAX-RS
- Health checks for Kubernetes
- Prometheus metrics integration
- Multiple Docker build options (JVM, native, micro)
- Complete test infrastructure
Commit Reference: b64377c
- Goal: Implement a computational endpoint with comprehensive testing
- Commit Reference: b3848b0
src/main/java/com/vorozco/FibonacciResource.java:
package com.vorozco;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.List;
@Path("/fibonacci")
public class FibonacciResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Long> getFibonacci(@QueryParam("fibo") Integer fibo) {
int iterations = (fibo != null && fibo > 0) ? fibo : 5;
List<Long> sequence = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
sequence.add(fibonacci(i));
}
return sequence;
}
private long fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
}src/test/java/com/vorozco/FibonacciResourceTest.java:
package com.vorozco;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest. Matchers.*;
@QuarkusTest
public class FibonacciResourceTest {
@Test
public void testFibonacciEndpointFibo2() {
given()
.queryParam("fibo", 2)
.when().get("/fibonacci")
.then()
.statusCode(200)
.body("size()", is(2))
.body("[0]", is(0))
.body("[1]", is(1));
}
}./mvnw test
curl "http://localhost:8080/fibonacci?fibo=7"
# Returns: [0,1,1,2,3,5,8]What this demonstrates:
- Query parameter handling
- JSON serialization
- REST Assured testing framework
- Integration testing patterns
Commit Reference: b3848b0
- Goal: Add automated code quality and security checks
- Commit Reference: 872235d
Add this plugin configuration to the <build><plugins> section:
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.8.2</version>
<dependencies>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.9.8</version>
</dependency>
</dependencies>
</plugin>Modify src/main/java/com/vorozco/GreetingResource.java:
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
var dummyVar = doSpotBugsDemo(); // Unused variable - SpotBugs will detect
return "Hello from Quarkus REST";
}
private String doSpotBugsDemo() {
var dummyVar = "This is not an error"; // Dead store
return "SpotBugs demo";
}
}./mvnw spotbugs:checkExpected output: SpotBugs will report:
- Dead store to local variable (dummyVar)
- Unused method result
What SpotBugs detects:
- Null pointer dereferences
- Infinite loops
- Synchronization issues
- Vulnerable code patterns
- Performance problems
- Bad practices
Commit Reference: 872235d
- Goal: Resolve SpotBugs warnings to achieve clean code
- Commit Reference: d959814
Update src/main/java/com/vorozco/GreetingResource.java:
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}./mvnw spotbugs:check
# Should pass with no warningsBest practice: Fix security and quality issues immediately in the development phase (shift-left approach).
Commit Reference: d959814
- Goal: Create an endpoint to verify container networking
- Commit Reference: ef407fe
src/main/java/com/vorozco/IpResource.java:
package com.vorozco;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Path("/ip")
@Produces(MediaType.TEXT_PLAIN)
public class IpResource {
@GET
public String getIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
return "Unknown";
}
}
}src/test/java/com/vorozco/IpResourceTest.java:
package com.vorozco;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesPattern;
import io.quarkus.test.junit.QuarkusTest;
import org. junit.jupiter.api.Test;
@QuarkusTest
public class IpResourceTest {
@Test
public void testServerIpEndpoint() {
// Regex for IPv4 and IPv6
String ipv4Pattern = "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
String ipv6Pattern = "^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$";
given()
.when()
.get("/ip")
.then()
.statusCode(200)
.body(
anyOf(
matchesPattern(ipv4Pattern),
matchesPattern(ipv6Pattern),
is("Unknown")
)
);
}
}docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-cloud-native-workload-jvm .# Local development
./mvnw quarkus:dev
curl http://localhost:8080/ip
# Docker container (after building)
docker run -p 8080:8080 quarkus/quarkus-cloud-native-workload-jvm
curl http://localhost:8080/ipUse case: Verify container networking, troubleshoot Kubernetes pods, demonstrate microservice communication.
Commit Reference: ef407fe
- Goal: Automate testing, code quality checks, and builds
- Commit Reference: f4800e7
Create .github/workflows/full-build.yml:
name: full-build. yml
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
quarkus:
name: "Quarkus"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "21"
cache: "maven"
- name: Run Maven tests
run: ./mvnw verify spotbugs:check
working-directory: quarkus-cloud-native-workload- Triggers: Runs on push to main and pull requests
- Checkout: Gets the latest code
- Java Setup: Installs JDK 21 with Maven caching
- Verification: Runs unit tests, integration tests, and SpotBugs
git add .github/workflows/full-build.yml
git commit -m "Add CI/CD pipeline"
git push origin mainVisit your repository's Actions tab to see the workflow run.
CI/CD benefits:
- Automated quality gates
- Consistent build environment
- Fast feedback loop
- Prevention of defects reaching production
Commit Reference: f4800e7
- Goal: Simplify container building and Kubernetes deployment
- Commit Reference: 23f7471
Add to <properties>:
<jkube.generator.name>docker. io/tuxtor/quarkus-cloud-native-workload:%l</jkube.generator.name>Add to <build><plugins>:
<plugin>
<groupId>org.eclipse.jkube</groupId>
<artifactId>kubernetes-maven-plugin</artifactId>
<version>1.18.2</version>
</plugin>./mvnw k8s:buildThis creates a Docker image with the tag specified in jkube.generator.name.
./mvnw k8s:resourcek3d cluster create k3d-cluster
# k3d cluster delete k3d-cluster./mvnw k8s:apply- Auto-detection: Analyzes your project and creates appropriate Kubernetes resources
- Zero-config: Works out of the box with sensible defaults
- Customizable: Override via XML configuration or resource fragments
- Multi-platform: Supports Kubernetes and OpenShift
Example generated resources:
- Deployment
- Service
Commit Reference: 23f7471
- Goal: Scan container images for vulnerabilities in the CI/CD pipeline
- Commit Reference: 2b4a793
Modify .github/workflows/full-build.yml to add Docker build and Trivy scan:
name: full-build.yml
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
quarkus:
name: "Quarkus"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "21"
cache: "maven"
- name: Run Maven tests
run: ./mvnw verify spotbugs:check
working-directory: quarkus-cloud-native-workload
- name: Docker build
run: ./mvnw k8s:build
working-directory: quarkus-cloud-native-workload
- name: Run Trivy security scan (Docker)
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: "image"
image-ref: docker.io/tuxtor/quarkus-cloud-native-workload:latest
format: "table"
exit-code: "0"
severity: 'CRITICAL,HIGH'- OS packages: Vulnerabilities in Alpine, Debian, Ubuntu, etc.
- Application dependencies: Java, Node.js, Python, Ruby, etc.
- Misconfigurations: Kubernetes manifests, Dockerfiles, Terraform
- Secrets: Hardcoded passwords, API keys, tokens
- License compliance: Dependency licenses
scan-type: "image": Scan Docker container imageseverity: 'CRITICAL,HIGH': Only report serious vulnerabilitiesexit-code: "0": Don't fail the build (change to "1" for strict enforcement)format: "table": Human-readable output
quarkus-cloud-native-workload: latest (alpine 3.18.0)
===========================================================
Total: 2 (HIGH: 2, CRITICAL: 0)
┌─────────────┬────────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
├─────────────┼────────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────┤
│ openssl │ CVE-2023-12345 │ HIGH │ 3.0.8-r0 │ 3.0.9-r0 │ OpenSSL vulnerability │
│ libcrypto │ CVE-2023-67890 │ HIGH │ 3.0.8-r0 │ 3.0.9-r0 │ Cryptographic weakness │
└─────────────┴────────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────┘
- Early detection: Find vulnerabilities before deployment
- Fast feedback: Developers get immediate results
- Cost savings: Cheaper to fix in development than production
- Compliance: Document security posture
- Prevention: Block vulnerable images from reaching production
For strict security enforcement, change exit-code to "1":
- name: Run Trivy security scan (Docker)
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: "image"
image-ref: docker.io/tuxtor/quarkus-cloud-native-workload:latest
format: "sarif"
output: "trivy-results.sarif"
exit-code: "1" # Fail the build on vulnerabilities
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: "trivy-results.sarif"Commit Reference: 2b4a793
Congratulations! You've built a complete DevSecOps pipeline incorporating:
- Development Environment: Cloud-based with GitHub Codespaces
- Modern Framework: Quarkus for cloud-native Java applications
- Comprehensive Testing: Unit and integration tests with REST Assured
- Code Quality: SpotBugs static analysis
- CI/CD Automation: GitHub Actions pipeline
- Containerization: Docker with Eclipse JKube
- Security Scanning: Trivy for vulnerability detection
- Cloud Native: Kubernetes-ready deployments
┌─────────────────┐
│ Code Commit │
└────────┬────────┘
│
▼
┌─────────────────┐
│ GitHub Actions │
│ Triggered │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Build & Test │──► Unit Tests
│ │──► Integration Tests
└────────┬────────┘
│
▼
┌─────────────────┐
│ SpotBugs Scan │──► Code Quality
│ │──► Security Issues
└────────┬────────┘
│
▼
┌─────────────────┐
│ Docker Build │──► JKube Build
│ │──► Container Image
└────────┬────────┘
│
▼
┌─────────────────┐
│ Trivy Scan │──► Vulnerability Check
│ │──► Compliance Check
└─────────────────┘
- Shift-Left Security: Security checks integrated early in development
- Automation First: Every step is automated and repeatable
- Continuous Feedback: Developers get immediate results
- Infrastructure as Code: Everything is versioned and reproducible
- Defense in Depth: Multiple security layers (static analysis, container scanning)
The Ops in DevSecOps :)
Full source code: https://github.com/tuxtor/devsecops-oci-workshop