Skip to content

Commit 747e9e0

Browse files
authored
Merge pull request #4599 from owncloud/feature/integrate_sbom
[FEATURE REQUEST] Create SBOM by using cyclonedx
2 parents c57d72a + a1aed36 commit 747e9e0

File tree

7 files changed

+321
-0
lines changed

7 files changed

+321
-0
lines changed

.github/workflows/sbom.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: SBOM
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_dispatch:
8+
pull_request:
9+
10+
jobs:
11+
sbom:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
18+
# Caches Gradle dependencies to avoid downloading them on every run
19+
- name: Cache Gradle dependencies
20+
uses: actions/cache@v3
21+
with:
22+
path: |
23+
~/.gradle/caches
24+
~/.gradle/wrapper
25+
~/.gradle/wrapper/dists/
26+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
27+
restore-keys: |
28+
${{ runner.os }}-gradle-
29+
30+
- name: Set up JDK 17
31+
uses: actions/setup-java@v4
32+
with:
33+
java-version: '17'
34+
distribution: 'temurin'
35+
36+
- name: Install xsltproc
37+
run: |
38+
sudo apt-get update
39+
sudo apt-get install -y xsltproc
40+
41+
# Use --no-daemon to prevent Gradle from running in the background
42+
- name: Generate SBOM (CycloneDX)
43+
run: ./gradlew --no-daemon cyclonedxBom
44+
45+
- name: Convert SBOM to HTML
46+
run: xsltproc sbom/cyclonedx-xml-to-html.xslt build/reports/bom.xml > sbom.html
47+
48+
# Create a specific artifact name using the branch name and timestamp
49+
- name: Set artifact name
50+
id: vars
51+
run: |
52+
BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}"
53+
SAFE_BRANCH=$(echo "$BRANCH" | tr '/' '-' | tr '[:upper:]' '[:lower:]')
54+
TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
55+
echo "artifact_name=sbom-${SAFE_BRANCH}-${TIMESTAMP}" >> $GITHUB_OUTPUT
56+
57+
- name: Rename SBOM XML and HTML files to match artifact name
58+
run: |
59+
mv sbom.html "${{ steps.vars.outputs.artifact_name }}.html"
60+
mv build/reports/bom.xml "${{ steps.vars.outputs.artifact_name }}.xml"
61+
mv build/reports/bom.json "${{ steps.vars.outputs.artifact_name }}.json"
62+
63+
- name: ZIP all the files
64+
run: |
65+
zip "${{ steps.vars.outputs.artifact_name }}.zip" \
66+
"${{ steps.vars.outputs.artifact_name }}.html" \
67+
"${{ steps.vars.outputs.artifact_name }}.xml" \
68+
"${{ steps.vars.outputs.artifact_name }}.json"
69+
70+
- name: Upload SBOM artifact
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: ${{ steps.vars.outputs.artifact_name }}
74+
path: ${{ steps.vars.outputs.artifact_name }}.zip

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ ownCloud admins and users.
5252
* Enhancement - Shares space in Android native file explorer: [#4515](https://github.com/owncloud/android/issues/4515)
5353
* Enhancement - Accessibility reports in 4.5.1: [#4568](https://github.com/owncloud/android/issues/4568)
5454
* Enhancement - Support for Kiteworks servers without client secret: [#4588](https://github.com/owncloud/android/issues/4588)
55+
* Enhancement - SBOM (Software Bill of Materials): [#4598](https://github.com/owncloud/android/issues/4598)
5556

5657
## Details
5758

@@ -186,6 +187,15 @@ ownCloud admins and users.
186187
https://github.com/owncloud/android/issues/4588
187188
https://github.com/owncloud/android/pull/4589
188189

190+
* Enhancement - SBOM (Software Bill of Materials): [#4598](https://github.com/owncloud/android/issues/4598)
191+
192+
SBOM to be generated in every PR via GitHub Actions with the list of all
193+
dependencies used in the code. Tool cyclonedx builds it, artifact is exported to
194+
xml and finally converted to html with a xlst template.
195+
196+
https://github.com/owncloud/android/issues/4598
197+
https://github.com/owncloud/android/pull/4599
198+
189199
# Changelog for ownCloud Android Client [4.5.1] (2025-04-03)
190200

191201
The following sections list the changes in ownCloud Android Client 4.5.1 relevant to

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ plugins {
2222
alias libs.plugins.sonarqube
2323
alias libs.plugins.ksp apply false
2424
alias libs.plugins.detekt
25+
alias libs.plugins.cyclonedx
2526
}
2627

2728
allprojects {

changelog/unreleased/4599

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Enhancement: SBOM (Software Bill of Materials)
2+
3+
SBOM to be generated in every PR via GitHub Actions with the list of all dependencies used in the code. Tool cyclonedx builds it, artifact is exported to xml and finally converted to html with a xlst template.
4+
5+
https://github.com/owncloud/android/issues/4598
6+
https://github.com/owncloud/android/pull/4599
7+

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ androidxTestMonitor = "1.6.1"
2222
androidxTestUiAutomator ="2.2.0"
2323
androidxWork = "2.8.1"
2424
coil = "2.2.2"
25+
cyclonedx = "2.3.1"
2526
detekt = "1.23.3"
2627
dexopener = "2.0.5"
2728
disklrucache = "2.0.2"
@@ -128,3 +129,4 @@ kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
128129
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
129130
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
130131
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
132+
cyclonedx = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" }

sbom/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Software Bill of Materials (SBOM)
2+
3+
---
4+
5+
## What is an SBOM?
6+
7+
An **SBOM (Software Bill of Materials)** is a detailed inventory of all software components, libraries, and dependencies included in a project. It provides transparency into its composition.
8+
9+
10+
## Why CycloneDX?
11+
12+
[CycloneDX](https://cyclonedx.org/) is a leading open standard for SBOMs that supports rich metadata and is widely supported by tools and ecosystems. It is especially suited for modern DevSecOps workflows and integrates well with build tools.
13+
14+
15+
## How to Generate an SBOM
16+
17+
The ownCloud Android app is ready to generate it. The dependency *CycloneDX* is included in the build.gradle file, so, the SBOM file is an easy one via gradlew:
18+
19+
```
20+
./gradlew cyclonedxBom
21+
```
22+
23+
that command will generate the sbom file in json and xml formats.
24+
25+
## Preview
26+
27+
Since XML and JSON are not easily human-readable formats, the `xsltproc` tool can be used to transform the SBOM into a more user-friendly and visually readable HTML document, with a XSLT template.
28+
29+
XSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML documents into other formats, such as HTML, plain text, or other XML structures. It uses a set of rules defined in an XSLT stylesheet to match and manipulate elements in the source XML, allowing the data to be presented in a more readable or usable format.
30+
31+
Now...
32+
33+
```
34+
xsltproc cyclonedx-xml-to-html.xslt bom.xml > bom.html
35+
```
36+
37+
And the `bom.html` file is ready to go.
38+
39+
40+

sbom/cyclonedx-xml-to-html.xslt

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xsl:stylesheet version="1.0"
3+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
4+
xmlns:bom="http://cyclonedx.org/schema/bom/1.6"
5+
exclude-result-prefixes="bom">
6+
7+
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
8+
9+
<xsl:template match="/">
10+
<html>
11+
<head>
12+
<title>Software Bill of Materials</title>
13+
<style>
14+
body {
15+
font-family: sans-serif;
16+
margin: 20px;
17+
background-color: #f0f4fb;
18+
color: #000;
19+
}
20+
h1 {
21+
text-align: left;
22+
color: #2c3e50;
23+
font-size: 2em;
24+
margin-bottom: 0.5em;
25+
}
26+
h2 {
27+
color: #3a4f7a;
28+
border-bottom: 2px solid #ccc;
29+
padding-bottom: 5px;
30+
margin-top: 40px;
31+
margin-bottom: 15px;
32+
}
33+
table {
34+
border-collapse: collapse;
35+
background-color: #fff;
36+
color: #000;
37+
margin-top: 10px;
38+
margin-bottom: 40px;
39+
border-radius: 6px;
40+
overflow: hidden;
41+
}
42+
.metadata-table {
43+
width: auto;
44+
margin: 0;
45+
padding: 0;
46+
}
47+
th, td {
48+
padding: 10px 10px;
49+
text-align: left;
50+
border-bottom: 1px solid #ccc;
51+
}
52+
tr {
53+
line-height: 2;
54+
}
55+
th {
56+
background-color: #d0def0;
57+
font-weight: bold;
58+
color: #1b2e4b;
59+
}
60+
a {
61+
color: #003366;
62+
text-decoration: none;
63+
}
64+
a:hover {
65+
text-decoration: underline;
66+
}
67+
.footer {
68+
margin-top: 50px;
69+
font-size: 0.9em;
70+
color: #666;
71+
text-align: center;
72+
}
73+
ul {
74+
list-style: none;
75+
padding: 0;
76+
margin: 10px 0 30px 0;
77+
}
78+
li {
79+
margin-bottom: 5px;
80+
}
81+
.index a {
82+
color: #000;
83+
text-decoration: underline;
84+
}
85+
.back-to-top {
86+
display: inline-block;
87+
margin-top: 20px;
88+
font-size: 0.9em;
89+
color: #003366;
90+
text-decoration: none;
91+
}
92+
.back-to-top::before {
93+
content: "↑ ";
94+
font-weight: bold;
95+
margin-right: 4px;
96+
}
97+
.back-to-top:hover {
98+
text-decoration: underline;
99+
}
100+
</style>
101+
</head>
102+
<body>
103+
<h1>Software Bill of Materials - ownCloud Android app</h1>
104+
105+
<div class="index">
106+
<h2>Index</h2>
107+
<ul>
108+
<li><a href="#metadata">Metadata</a></li>
109+
<li><a href="#components">Components</a></li>
110+
</ul>
111+
</div>
112+
113+
<!-- Metadata Section -->
114+
<h2 id="metadata">Metadata</h2>
115+
<table class="metadata-table">
116+
<tr><th>Creation Date</th><td><xsl:call-template name="format-date"><xsl:with-param name="timestamp" select="bom:bom/bom:metadata/bom:timestamp"/></xsl:call-template></td></tr>
117+
<tr><th>Generated by</th>
118+
<td>
119+
<xsl:for-each select="bom:bom/bom:metadata/bom:tools/bom:components/bom:component">
120+
<xsl:value-of select="bom:name"/> <xsl:text> </xsl:text><xsl:value-of select="bom:version"/>
121+
</xsl:for-each>
122+
</td>
123+
</tr>
124+
</table>
125+
126+
<!-- Components Section -->
127+
<h2 id="components">Components</h2>
128+
<table style="width: 100%;">
129+
<tr>
130+
<th style="width: 10%;">Type</th>
131+
<th style="width: 15%;">Group</th>
132+
<th style="width: 20%;">Name</th>
133+
<th style="width: 10%;">Version</th>
134+
<th style="width: 10%;">License</th>
135+
<th style="width: 35%;">PURL</th>
136+
</tr>
137+
<xsl:for-each select="bom:bom/bom:components/bom:component">
138+
<tr>
139+
<td><xsl:value-of select="@type"/></td>
140+
<td><xsl:value-of select="bom:group"/></td>
141+
<td><xsl:value-of select="bom:name"/></td>
142+
<td><xsl:value-of select="bom:version"/></td>
143+
<td><xsl:value-of select="bom:licenses/bom:license/bom:id"/></td>
144+
<td><xsl:value-of select="bom:purl"/></td>
145+
</tr>
146+
</xsl:for-each>
147+
</table>
148+
149+
<div><a class="back-to-top" href="#metadata">Back to top</a></div>
150+
151+
<div class="footer">
152+
Document generated from CycloneDX SBOM using XSLT styling.
153+
</div>
154+
</body>
155+
</html>
156+
</xsl:template>
157+
158+
<!-- Helper: Convert ISO timestamp to readable date -->
159+
<xsl:template name="format-date">
160+
<xsl:param name="timestamp"/>
161+
<xsl:variable name="year" select="substring($timestamp, 1, 4)"/>
162+
<xsl:variable name="month" select="substring($timestamp, 6, 2)"/>
163+
<xsl:variable name="day" select="substring($timestamp, 9, 2)"/>
164+
<xsl:variable name="hour" select="substring($timestamp, 12, 2)"/>
165+
<xsl:variable name="minute" select="substring($timestamp, 15, 2)"/>
166+
<xsl:variable name="monthName">
167+
<xsl:choose>
168+
<xsl:when test="$month = '01'">January</xsl:when>
169+
<xsl:when test="$month = '02'">February</xsl:when>
170+
<xsl:when test="$month = '03'">March</xsl:when>
171+
<xsl:when test="$month = '04'">April</xsl:when>
172+
<xsl:when test="$month = '05'">May</xsl:when>
173+
<xsl:when test="$month = '06'">June</xsl:when>
174+
<xsl:when test="$month = '07'">July</xsl:when>
175+
<xsl:when test="$month = '08'">August</xsl:when>
176+
<xsl:when test="$month = '09'">September</xsl:when>
177+
<xsl:when test="$month = '10'">October</xsl:when>
178+
<xsl:when test="$month = '11'">November</xsl:when>
179+
<xsl:when test="$month = '12'">December</xsl:when>
180+
<xsl:otherwise>Unknown</xsl:otherwise>
181+
</xsl:choose>
182+
</xsl:variable>
183+
<xsl:value-of select="concat($day, ' ', $monthName, ' ', $year, ', ', $hour, ':', $minute, ' UTC')"/>
184+
</xsl:template>
185+
186+
</xsl:stylesheet>
187+

0 commit comments

Comments
 (0)