Introduction
In today's software landscape, securing the software supply chain is paramount. Tekton Chains provides a critical layer of security by generating cryptographically signed provenance metadata for your CI/CD pipelines. For Java applications, this means you can verify the origin, integrity, and build process of every artifact deployed to production. This guide explores how to implement Tekton Chains to create verifiable software supply chains for your Java workloads.
Article: Implementing Software Supply Chain Security with Tekton Chains for Java
Tekton Chains is a Kubernetes-native solution that captures, signs, and stores provenance information from Tekton pipelines. For Java applications, this enables you to answer critical questions: Who built this JAR? What source code was used? Which dependencies were included? Was the build secure?
1. Understanding Provenance for Java Applications
What is Provenance?
Provenance is verifiable information about the origin and history of software artifacts. For Java applications, this includes:
- Source Code: Git commit hashes, repository URLs
- Build Environment: Base images, build tools, environment variables
- Dependencies: Maven/Gradle dependencies, their checksums
- Build Process: Steps executed, parameters used
- Output Artifacts: JAR/WAR files, Docker images, SBOMs
Why Java Applications Need Provenance:
- Security Compliance: Meet regulatory requirements (SLSA, NIST)
- Vulnerability Management: Trace dependencies to specific versions
- Audit Trail: Prove who built what and when
- Reproducible Builds: Verify build integrity across environments
2. Tekton Chains Architecture Overview
Java Source Code ↓ Tekton Pipeline ↓ Tekton Chains (Provenance Capture) ↓ Cryptographic Signing ↓ Storage (OCI Registry, Transparency Log) ↓ Verification & Policy Enforcement
3. Setting Up Tekton Chains
Prerequisites:
- Kubernetes cluster (1.21+)
- Tekton Pipelines v0.40+
- Tekton Chains v0.15+
- Cosign for verification
- Sigstore for certificate transparency
Installation:
# tekton-chains.yaml apiVersion: operator.tekton.dev/v1alpha1 kind: TektonChain metadata: name: chain spec: targetNamespace: tekton-chains chain: artifacts.oci.storage: oci artifacts.oci.signer: x509 transparency.enabled: "true" taskrun.format: in-toto taskrun.storage: tekton
4. Java-Specific Tekton Pipeline for Provenance
Complete Java Build Pipeline:
# java-build-pipeline.yaml apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: java-build-pipeline annotations: chains.tekton.dev/signed: "true" spec: workspaces: - name: source - name: maven-cache - name: cosign-key params: - name: image-url type: string - name: git-revision type: string - name: java-version type: string default: "17" tasks: - name: fetch-sources taskRef: name: git-clone workspaces: - name: output workspace: source params: - name: url value: "$(params.git-url)" - name: revision value: "$(params.git-revision)" - name: build-java taskRef: name: maven-build runAfter: ["fetch-sources"] workspaces: - name: source workspace: source - name: maven-cache workspace: maven-cache params: - name: JAVA_VERSION value: "$(params.java-version)" - name: MAVEN_GOALS value: "package" - name: MAVEN_OPTS value: "-Dmaven.test.skip=false" - name: run-tests taskRef: name: maven-test runAfter: ["build-java"] workspaces: - name: source workspace: source params: - name: MAVEN_GOALS value: "verify" - name: generate-sbom taskRef: name: syft-sbom runAfter: ["build-java"] workspaces: - name: source workspace: source params: - name: IMAGE value: "$(params.image-url)" - name: build-image taskRef: name: kaniko-build runAfter: ["run-tests", "generate-sbom"] workspaces: - name: source workspace: source params: - name: IMAGE value: "$(params.image-url)" - name: DOCKERFILE value: "./Dockerfile" - name: sign-image taskRef: name: cosign-sign runAfter: ["build-image"] workspaces: - name: cosign-key workspace: cosign-key params: - name: IMAGE value: "$(params.image-url)" - name: KEY value: "cosign.key" - name: upload-provenance taskRef: name: upload-sbom-provenance runAfter: ["sign-image"] workspaces: - name: source workspace: source params: - name: IMAGE value: "$(params.image-url)"
Maven Build Task:
# maven-build-task.yaml apiVersion: tekton.dev/v1 kind: Task metadata: name: maven-build annotations: chains.tekton.dev/signed: "true" spec: workspaces: - name: source - name: maven-cache params: - name: JAVA_VERSION type: string default: "17" - name: MAVEN_GOALS type: string default: "package" - name: MAVEN_OPTS type: string default: "" steps: - name: build image: maven:3.8.6-openjdk-$(params.JAVA_VERSION)-slim workingDir: $(workspaces.source.path) env: - name: MAVEN_OPTS value: "$(params.MAVEN_OPTS) -Dmaven.repo.local=$(workspaces.maven-cache.path)/.m2/repository" command: ["mvn"] args: - "$(params.MAVEN_GOALS)" - "-DskipTests=false" - "-Dmaven.test.failure.ignore=false" volumeMounts: - name: maven-cache mountPath: "/tmp/maven-cache" - name: record-build-info image: alpine/git:latest workingDir: $(workspaces.source.path) script: | # Capture build information for provenance echo "GIT_COMMIT=$(git rev-parse HEAD)" >> /tekton/build-info/build.env echo "GIT_REPO=$(git config --get remote.origin.url)" >> /tekton/build-info/build.env echo "BUILD_TIMESTAMP=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> /tekton/build-info/build.env # Capture Java version information java -version 2>&1 | head -1 >> /tekton/build-info/java-version.txt mvn -version >> /tekton/build-info/maven-version.txt
SBOM Generation Task:
# sbom-generation-task.yaml apiVersion: tekton.dev/v1 kind: Task metadata: name: syft-sbom annotations: chains.tekton.dev/signed: "true" spec: workspaces: - name: source params: - name: IMAGE type: string steps: - name: generate-sbom image: anchore/syft:latest workingDir: $(workspaces.source.path) command: ["syft"] args: - "packages" - "dir:$(workspaces.source.path)" - "--output" - "spdx-json" - "--file" - "/tekton/results/sbom.spdx.json" - name: attest-sbom image: gcr.io/projectsigstore/cosign:v2.0.0 workingDir: $(workspaces.source.path) command: ["cosign"] args: - "attest" - "--predicate" - "/tekton/results/sbom.spdx.json" - "--type" - "spdxjson" - "$(params.IMAGE)"
5. Java-Specific Provenance Configuration
Custom Task for Java Dependency Analysis:
# java-dependency-task.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: java-dependency-analysis
annotations:
chains.tekton.dev/signed: "true"
spec:
workspaces:
- name: source
steps:
- name: analyze-dependencies
image: maven:3.8.6-openjdk-17-slim
workingDir: $(workspaces.source.path)
script: |
# Generate dependency tree for provenance
mvn dependency:tree -DoutputFile=/tekton/results/dependency-tree.txt
# Generate dependency list with versions
mvn dependency:list -DincludeArtifactIds=true -DoutputFile=/tekton/results/dependencies.txt
# Generate Software Bill of Materials (SBOM)
mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.4:makeAggregateBom
cp target/bom.json /tekton/results/java-sbom.json
# Capture vulnerability scan results
mvn org.owasp:dependency-check-maven:7.4.4:check -Dformat=JSON
cp target/dependency-check-report.json /tekton/results/vulnerability-scan.json
- name: record-provenance
image: alpine:latest
script: |
# Create comprehensive build provenance
cat > /tekton/results/provenance.json << EOF
{
"buildInfo": {
"javaVersion": "$(java -version 2>&1 | head -1)",
"mavenVersion": "$(mvn -version | head -1)",
"buildTimestamp": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
},
"source": {
"gitCommit": "$(git rev-parse HEAD)",
"gitRepo": "$(git config --get remote.origin.url)",
"branch": "$(git branch --show-current)"
},
"dependencies": {
"count": $(mvn dependency:list -q -DincludeArtifactIds=true | wc -l),
"vulnerabilities": $(jq '.dependencies | length' /tekton/results/vulnerability-scan.json)
}
}
EOF
6. PipelineRun with Provenance Capture
Complete PipelineRun:
# java-pipeline-run.yaml apiVersion: tekton.dev/v1 kind: PipelineRun metadata: name: java-order-service-build-$(uid) annotations: chains.tekton.dev/signed: "true" chains.tekton.dev/format: "in-toto" spec: pipelineRef: name: java-build-pipeline params: - name: image-url value: "gcr.io/my-project/order-service:$(uid)" - name: git-revision value: "main" - name: java-version value: "17" workspaces: - name: source volumeClaimTemplate: spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi - name: maven-cache persistentVolumeClaim: claimName: maven-cache-pvc - name: cosign-key secret: secretName: cosign-key
7. Tekton Chains Configuration for Java
Chains ConfigMap:
# chains-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: chains-config namespace: tekton-chains data: artifacts.oci.format: "simplesigning" artifacts.oci.storage: "oci,tekton" artifacts.oci.signer: "x509" artifacts.taskrun.format: "in-toto" artifacts.taskrun.storage: "tekton" artifacts.taskrun.signer: "x509" transparency.enabled: "true" # Java-specific provenance settings artifacts.taskrun.enabled: "true" artifacts.oci.enabled: "true" # Storage backends storage.tekton.host: "http://tekton-pipelines-controller:9090" storage.oci.repository: "gcr.io/my-project/tekton-chains" # Signing key configuration signers.x509.identity.simple_signing: "k8s://tekton-chains/signing-secrets"
8. Verification and Policy Enforcement
Verification with Cosign:
// Java-based verification utility
public class ProvenanceVerifier {
public boolean verifyImageSignature(String imageRef) {
try {
Process process = new ProcessBuilder(
"cosign", "verify",
"--key", "cosign.pub",
imageRef
).start();
return process.waitFor() == 0;
} catch (Exception e) {
throw new RuntimeException("Failed to verify image signature", e);
}
}
public boolean verifyProvenance(String imageRef, String expectedDigest) {
try {
// Verify SLSA provenance
Process process = new ProcessBuilder(
"cosign", "verify-attestation",
"--type", "slsaprovenance",
"--key", "cosign.pub",
imageRef
).start();
if (process.waitFor() != 0) {
return false;
}
// Extract and verify provenance content
String provenance = extractProvenance(imageRef);
return verifyProvenanceContent(provenance, expectedDigest);
} catch (Exception e) {
throw new RuntimeException("Failed to verify provenance", e);
}
}
private boolean verifyProvenanceContent(String provenance, String expectedDigest) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(provenance);
// Verify subject digest matches expected
String actualDigest = root.path("subject")
.get(0)
.path("digest")
.path("sha256")
.asText();
return expectedDigest.equals(actualDigest);
} catch (Exception e) {
return false;
}
}
}
Kubernetes Admission Controller for Verification:
# verification-policy.yaml apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: java-app-verification spec: images: - glob: "gcr.io/my-project/order-service/**" authorities: - key: data: | -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgA... -----END PUBLIC KEY----- attestations: - name: slsa-provenance predicateType: https://slsa.dev/provenance/v0.2 policy: type: cue data: | predicateType: "https://slsa.dev/provenance/v0.2" predicate: buildDefinition: buildType: "https://tekton.dev/chains/v2" runDetails: builder: id: "https://tekton.dev/chains/v2"
9. Java Application with Built-in Provenance Verification
Spring Boot Configuration:
@Configuration
@Slf4j
public class ProvenanceConfiguration {
@Value("${app.provenance.verification.enabled:true}")
private boolean verificationEnabled;
@Bean
public ProvenanceVerifier provenanceVerifier() {
return new ProvenanceVerifier();
}
@EventListener(ApplicationReadyEvent.class)
public void verifyOnStartup() {
if (!verificationEnabled) {
log.warn("Provenance verification is disabled");
return;
}
String imageRef = System.getenv("CONTAINER_IMAGE");
if (imageRef == null) {
log.error("CONTAINER_IMAGE environment variable not set");
return;
}
try {
boolean verified = provenanceVerifier().verifyImageSignature(imageRef);
if (verified) {
log.info("✅ Container image signature verified: {}", imageRef);
} else {
log.error("❌ Container image signature verification failed: {}", imageRef);
// In production, you might want to exit here
// System.exit(1);
}
} catch (Exception e) {
log.error("Failed to verify container image signature", e);
}
}
}
Build Information Endpoint:
@RestController
@RequestMapping("/api/system")
public class BuildInfoController {
@GetMapping("/build-info")
public ResponseEntity<Map<String, Object>> getBuildInfo() {
Map<String, Object> buildInfo = new HashMap<>();
// Read from generated build-info.properties
buildInfo.put("version", getClass().getPackage().getImplementationVersion());
buildInfo.put("buildTime", getClass().getPackage().getImplementationVendor());
// Add provenance information if available
buildInfo.put("provenance", readProvenanceInfo());
return ResponseEntity.ok(buildInfo);
}
@GetMapping("/provenance")
public ResponseEntity<Map<String, Object>> getProvenance() {
try {
String provenance = Files.readString(Path.of("/tekton/provenance.json"));
ObjectMapper mapper = new ObjectMapper();
return ResponseEntity.ok(mapper.readValue(provenance, Map.class));
} catch (Exception e) {
return ResponseEntity.status(404).build();
}
}
}
10. CI/CD Integration
GitHub Actions Workflow:
name: Java Build with Provenance
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Build with Maven
run: mvn -B package -DskipTests
- name: Generate SBOM
run: |
mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.4:makeAggregateBom
cp target/bom.json sbom.json
- name: Upload Build Results
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: |
target/*.jar
sbom.json
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Verify Provenance
run: |
# Verify the built image has proper provenance
cosign verify-attestation \
--type slsaprovenance \
--key cosign.pub \
$IMAGE
# Check SLSA level
slsa-verifier verify-artifact \
--provenance-path provenance.json \
--source-uri github.com/${{ github.repository }} \
target/application.jar
11. Monitoring and Auditing
Provenance Dashboard:
# provenance-monitoring.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: provenance-audit
spec:
schedule: "0 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: audit
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
# Check recent builds for provenance compliance
kubectl get taskruns -l chains.tekton.dev/signed=true \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.creationTimestamp}{"\t"}{.status.conditions[*].type}{"\n"}{end}'
# Verify stored provenance
cosign verify-attestation --key cosign.pub gcr.io/my-project/order-service:latest
12. Best Practices for Java Applications
1. Dependency Management:
# Ensure all dependencies are captured in SBOM - name: capture-dependencies image: maven:3.8.6-openjdk-17-slim script: | # Use CycloneDX for comprehensive Java SBOM mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom
2. Reproducible Builds:
# Dockerfile for reproducible Java builds FROM eclipse-temurin:17-jdk as builder WORKDIR /workspace COPY . . RUN ./mvnw -DskipTests package FROM eclipse-temurin:17-jre COPY --from=builder /workspace/target/*.jar app.jar # Add provenance metadata LABEL org.opencontainers.image.source="https://github.com/myorg/myapp"
3. Secure Signing:
# Use KMS or cloud-based key management spec: params: - name: signer value: "kms://projects/my-project/locations/global/keyRings/tekton/cryptoKeys/signing"
Benefits for Java Applications
- Supply Chain Security - Verifiable build and dependency information
- Compliance - Meet SLSA Level 2+ requirements
- Audit Trail - Complete history of builds and deployments
- Vulnerability Management - Trace dependencies to specific versions
- Reproducible Builds - Verify build integrity across environments
Conclusion
Tekton Chains provides a powerful foundation for establishing software supply chain security for Java applications. By capturing cryptographically signed provenance metadata from your CI/CD pipelines, you can verify the integrity and origin of every artifact deployed to production.
For Java development teams, this means:
- Trustworthy Builds - Verified build processes and dependencies
- Security Compliance - Meeting organizational and regulatory requirements
- Rapid Incident Response - Quickly identify affected components during security incidents
- Improved Governance - Complete audit trail of software changes
Start by implementing basic provenance capture for your Java builds, then gradually add more sophisticated verification and policy enforcement as your security maturity grows.
Call to Action: Begin by installing Tekton Chains in your development cluster and configuring it to capture basic build information from your Java pipelines. Start with signing your container images and gradually add SBOM generation and vulnerability scanning to create a comprehensive software supply chain security solution.
Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)
https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.
https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.
https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.
https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.
https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.
https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.
https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.
https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.
https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.