Skip to content
This repository was archived by the owner on Sep 14, 2026. It is now read-only.

Commit 3f36d15

Browse files
authored
Add SBOM release artifacts
1 parent f3b36ad commit 3f36d15

5 files changed

Lines changed: 85 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ jobs:
1313
runs-on: ubuntu-latest
1414
permissions:
1515
contents: write
16+
id-token: write
17+
attestations: write
18+
artifact-metadata: write
1619
steps:
1720
- name: Checkout
1821
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -27,9 +30,18 @@ jobs:
2730
run: |
2831
chmod +x ./gradlew
2932
./gradlew check releaseBundle
33+
- name: Generate release provenance attestation
34+
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
35+
with:
36+
subject-path: build/release/network-chat-*-windows.zip
37+
- name: Generate SBOM attestation
38+
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
39+
with:
40+
subject-path: build/release/network-chat-*-windows.zip
41+
sbom-path: build/release/network-chat-*-sbom.cdx.json
3042
- name: Upload release assets
3143
if: github.event_name == 'release'
3244
env:
3345
GH_TOKEN: ${{ github.token }}
3446
run: |
35-
gh release upload "${GITHUB_REF_NAME}" build/release/*.zip build/release/checksums.txt build/release/provenance.json --clobber
47+
gh release upload "${GITHUB_REF_NAME}" build/release/*.zip build/release/checksums.txt build/release/provenance.json build/release/*-sbom.cdx.json --clobber

README.en.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Network Chat is a Java 21 chat application over TCP sockets with:
1919
- optional file-backed message history with room replay after server restart.
2020
- optional TLS mode, token-based accounts with `USER`/`ADMIN` roles, and the admin `/health`
2121
command.
22-
- a Windows release zip with launch scripts, checksums, and provenance metadata.
22+
- a Windows release zip with launch scripts, checksums, CycloneDX SBOM, and provenance metadata.
2323
- reproducible Gradle build, tests, CI, and quality gates.
2424

2525
![Swing GUI client](docs/images/gui-client.svg)
@@ -84,7 +84,14 @@ To build a no-Gradle release package for end users:
8484
./gradlew releaseBundle
8585
```
8686

87-
Artifacts are written to `build/release`: the Windows zip, `checksums.txt`, and `provenance.json`.
87+
Artifacts are written to `build/release`: the Windows zip, `checksums.txt`, CycloneDX SBOM, and `provenance.json`.
88+
89+
Release verification:
90+
91+
```bash
92+
sha256sum -c checksums.txt
93+
gh attestation verify network-chat-*-windows.zip --repo krotname/JavaNetworkChat
94+
```
8895

8996
## Architecture and protocol
9097

@@ -177,6 +184,7 @@ This repository is organized for maintainability:
177184
- Automated quality gates via Checkstyle, Spotless, SpotBugs and JaCoCo.
178185
- Security checks via CodeQL and OpenSSF Scorecard.
179186
- Dependency and workflow automation via Dependabot.
187+
- Release artifacts: zip, `checksums.txt`, CycloneDX SBOM, provenance metadata, and GitHub attestations.
180188
- Explicit contributor and security docs.
181189

182190
## Troubleshooting

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Network Chat — это Java 21 приложение для сетевого ч
2424
- опциональная файловая история сообщений с replay последних сообщений комнаты после рестарта;
2525
- опциональный TLS-режим, token-based accounts с ролями `USER`/`ADMIN` и admin-команда
2626
`/health`;
27-
- Windows release zip с launch scripts, checksums и provenance metadata;
27+
- Windows release zip с launch scripts, checksums, CycloneDX SBOM и provenance metadata;
2828
- воспроизводимая Gradle-сборка, тесты, CI и проверки качества.
2929

3030
![Swing GUI client](docs/images/gui-client.svg)
@@ -82,7 +82,14 @@ $env:NETWORK_CHAT_TRUSTSTORE_PASSWORD="changeit"
8282
./gradlew releaseBundle
8383
```
8484

85-
Артефакты появятся в `build/release`: Windows zip, `checksums.txt` и `provenance.json`.
85+
Артефакты появятся в `build/release`: Windows zip, `checksums.txt`, CycloneDX SBOM и `provenance.json`.
86+
87+
Проверка релиза:
88+
89+
```bash
90+
sha256sum -c checksums.txt
91+
gh attestation verify network-chat-*-windows.zip --repo krotname/JavaNetworkChat
92+
```
8693

8794
## Архитектура и протокол
8895

@@ -160,6 +167,7 @@ Actions Summary для Linux job.
160167
- Авто-проверки: Checkstyle, Spotless, SpotBugs, JaCoCo.
161168
- Security проверки: CodeQL и OpenSSF Scorecard.
162169
- Dependabot с группировкой обновлений зависимостей и Actions.
170+
- Релизные артефакты: zip, `checksums.txt`, CycloneDX SBOM, provenance metadata и GitHub attestations.
163171
- Явно оформленные файлы `CONTRIBUTING.md` и `SECURITY.md`.
164172

165173
## Roadmap

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Protected assets:
3434
- chat message contents in transit when TLS is enabled,
3535
- account tokens stored only as salted SHA-256 hashes in the optional accounts file,
3636
- server availability under configured client limits,
37-
- release artifacts and their checksums/provenance metadata.
37+
- release artifacts, CycloneDX SBOM, checksums, provenance metadata, and GitHub attestations.
3838

3939
Trust boundaries:
4040

@@ -59,4 +59,4 @@ Operational recommendations:
5959
- Enable TLS outside localhost/trusted lab networks.
6060
- Keep `accounts.csv` readable only by the server operator.
6161
- Rotate tokens by replacing account-file rows and restarting the server.
62-
- Publish release zip files together with `checksums.txt` and `provenance.json`.
62+
- Publish release zip files together with `checksums.txt`, CycloneDX SBOM, `provenance.json`, and GitHub attestations.

build.gradle.kts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import java.security.MessageDigest
22
import java.time.Instant
3+
import java.util.UUID
4+
import groovy.json.JsonOutput
35

46
plugins {
57
java
@@ -267,6 +269,7 @@ val packageWindowsZip = tasks.register<Zip>("packageWindowsZip") {
267269
}
268270

269271
val provenanceFile = layout.buildDirectory.file("release/provenance.json")
272+
val sbomFile = layout.buildDirectory.file("release/$releasePackageName-sbom.cdx.json")
270273

271274
tasks.register("releaseProvenance") {
272275
group = "distribution"
@@ -306,15 +309,16 @@ tasks.register("releaseProvenance") {
306309
tasks.register("releaseChecksums") {
307310
group = "distribution"
308311
description = "Write SHA-256 checksums for release artifacts"
309-
dependsOn(packageWindowsZip, tasks.named("releaseProvenance"))
312+
dependsOn(packageWindowsZip, tasks.named("releaseProvenance"), "releaseSbom")
310313
val checksumsFile = layout.buildDirectory.file("release/checksums.txt")
311-
inputs.files(packageWindowsZip.flatMap { it.archiveFile }, provenanceFile)
314+
inputs.files(packageWindowsZip.flatMap { it.archiveFile }, provenanceFile, sbomFile)
312315
outputs.file(checksumsFile)
313316
doLast {
314317
val artifacts =
315318
listOf(
316319
packageWindowsZip.get().archiveFile.get().asFile,
317320
provenanceFile.get().asFile,
321+
sbomFile.get().asFile,
318322
)
319323
checksumsFile.get().asFile.writeText(
320324
artifacts.joinToString(System.lineSeparator()) {
@@ -324,10 +328,52 @@ tasks.register("releaseChecksums") {
324328
}
325329
}
326330

331+
tasks.register("releaseSbom") {
332+
group = "distribution"
333+
description = "Write a CycloneDX SBOM for the release runtime classpath"
334+
outputs.file(sbomFile)
335+
doLast {
336+
val components =
337+
configurations.runtimeClasspath.get().resolvedConfiguration.resolvedArtifacts
338+
.sortedWith(compareBy({ it.moduleVersion.id.group }, { it.name }, { it.moduleVersion.id.version }))
339+
.map {
340+
val id = it.moduleVersion.id
341+
mapOf(
342+
"type" to "library",
343+
"bom-ref" to "pkg:maven/${id.group}/${id.name}@${id.version}",
344+
"group" to id.group,
345+
"name" to id.name,
346+
"version" to id.version,
347+
"purl" to "pkg:maven/${id.group}/${id.name}@${id.version}",
348+
)
349+
}
350+
val bom =
351+
mapOf(
352+
"bomFormat" to "CycloneDX",
353+
"specVersion" to "1.6",
354+
"serialNumber" to "urn:uuid:${UUID.nameUUIDFromBytes("${project.name}:${project.version}".toByteArray())}",
355+
"version" to 1,
356+
"metadata" to
357+
mapOf(
358+
"timestamp" to Instant.now().toString(),
359+
"component" to
360+
mapOf(
361+
"type" to "application",
362+
"name" to project.name,
363+
"version" to project.version.toString(),
364+
"purl" to "pkg:maven/${project.group}/${project.name}@${project.version}",
365+
),
366+
),
367+
"components" to components,
368+
)
369+
sbomFile.get().asFile.writeText(JsonOutput.prettyPrint(JsonOutput.toJson(bom)))
370+
}
371+
}
372+
327373
tasks.register("releaseBundle") {
328374
group = "distribution"
329-
description = "Build release zip, checksums, and provenance metadata"
330-
dependsOn(packageWindowsZip, tasks.named("releaseProvenance"), tasks.named("releaseChecksums"))
375+
description = "Build release zip, checksums, SBOM, and provenance metadata"
376+
dependsOn(packageWindowsZip, tasks.named("releaseProvenance"), tasks.named("releaseSbom"), tasks.named("releaseChecksums"))
331377
}
332378

333379
fun sha256(file: File): String {

0 commit comments

Comments
 (0)