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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Binaries
ubuntu-sbom
sbom-merge
ubuntu-nix-sbom
ubuntu-sbom-arm64

# SBOM outputs
*.spdx.json
Expand All @@ -15,6 +17,11 @@ go.sum
# Nix
result
result-*
.envrc
.direnv

# Pre-commit
.pre-commit-config.yaml

# IDE
.vscode/
Expand Down
68 changes: 64 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ A comprehensive SBOM (Software Bill of Materials) generator for systems running
- **License Detection**: Extracts license information from package metadata
- **Package URLs (purl)**: Includes purl references for both deb and nix packages

## Quick Run

No installation required! Run directly from GitHub:

### Generate Merged SBOM (Ubuntu + Nix)

```bash
# Generate combined SBOM for your system
nix run --extra-experimental-features "nix-command flakes" github:supabase/ubuntu-nix-sbom#sbom-generator -- \
--nix-target /nix/var/nix/profiles/system \
--output system-sbom.json
```

### Generate Ubuntu-Only SBOM

```bash
# Scan only Ubuntu/Debian packages
nix run --extra-experimental-features "nix-command flakes" github:supabase/ubuntu-nix-sbom#sbom-ubuntu -- \
--output ubuntu-sbom.json
```

### Generate Nix-Only SBOM

```bash
# Analyze a specific Nix derivation
nix run --extra-experimental-features "nix-command flakes" github:supabase/ubuntu-nix-sbom#sbom-nix -- \
/nix/store/xxx-your-derivation \
--output nix-sbom.json
```

## Prerequisites

- Nix with flakes enabled
Expand All @@ -22,7 +52,7 @@ A comprehensive SBOM (Software Bill of Materials) generator for systems running
Clone the repository and enter the development shell:

```bash
git clone <repository-url>
git clone https://github.com/supabase/ubuntu-nix-sbom
cd ubuntu-nix-sbom
nix develop
```
Expand Down Expand Up @@ -283,24 +313,54 @@ Both the PR check and release workflows automatically validate SPDX output:

## Development

### Project Structure

The project follows the standard Go project layout:

```
.
├── internal/
│ └── sbom/
│ └── types.go # Shared types and utilities (package sbom)
├── cmd/
│ ├── ubuntu-sbom/
│ │ └── main.go # Ubuntu SBOM generator binary
│ └── sbom-merge/
│ └── main.go # SBOM merger binary
├── flake.nix # Nix flake configuration
└── go.mod # Go module (github.com/supabase/ubuntu-nix-sbom)
```

The `internal/` directory is a Go convention that prevents external packages from importing the code, ensuring the shared types remain internal to this project.

### Development Environment

Enter the development shell:

```bash
nix develop
```

This provides:
- Go toolchain
- Go toolchain (with Go modules support)
- gopls (Go language server)
- sbomnix
- Formatting tools (nixfmt, shellcheck, shfmt, gofmt)
- Pre-commit hooks (automatically installed)

### Building Binaries

Build the Go binaries manually:

```bash
go build -o ubuntu-sbom main.go
go build -o sbom-merge merge.go
# Build Ubuntu SBOM generator
go build -o ubuntu-sbom ./cmd/ubuntu-sbom

# Build SBOM merger
go build -o sbom-merge ./cmd/sbom-merge

# Build both
go build ./cmd/...
```

### Code Formatting
Expand Down
32 changes: 17 additions & 15 deletions merge.go → cmd/sbom-merge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"regexp"
"strings"
"time"

"github.com/supabase/ubuntu-nix-sbom/internal/sbom"
)

func mainMerge() {
func main() {
var (
ubuntuSBOM = flag.String("ubuntu", "", "Path to Ubuntu SBOM JSON file")
nixSBOM = flag.String("nix", "", "Path to Nix SBOM JSON file")
Expand All @@ -38,7 +40,7 @@ func mainMerge() {

type SBOMMerger struct{}

func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*sbom.SPDXDocument, error) {
// Load Ubuntu SBOM
ubuntuDoc, err := m.loadDocument(ubuntuPath)
if err != nil {
Expand All @@ -52,23 +54,23 @@ func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
}

// Create merged document
mergedDoc := &SPDXDocument{
mergedDoc := &sbom.SPDXDocument{
SPDXVersion: "SPDX-2.3",
DataLicense: "CC0-1.0",
SPDXID: "SPDXRef-DOCUMENT",
Name: fmt.Sprintf("Ubuntu-Nix-System-SBOM-%s", time.Now().Format("2006-01-02")),
DocumentNamespace: fmt.Sprintf("https://sbom.ubuntu-nix.system/%s", generateUUID()),
CreationInfo: CreationInfo{
DocumentNamespace: fmt.Sprintf("https://sbom.ubuntu-nix.system/%s", sbom.GenerateUUID()),
CreationInfo: sbom.CreationInfo{
Created: time.Now().UTC().Format(time.RFC3339),
Creators: m.mergeCreators(ubuntuDoc, nixDoc),
LicenseListVersion: "3.20",
},
Packages: []Package{},
Relationships: []Relationship{},
Packages: []sbom.Package{},
Relationships: []sbom.Relationship{},
}

// Create the single root System package
systemPkg := Package{
systemPkg := sbom.Package{
SPDXID: "SPDXRef-System",
Name: "Ubuntu-Nix-System",
DownloadLocation: "NOASSERTION",
Expand All @@ -81,7 +83,7 @@ func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
mergedDoc.Packages = append(mergedDoc.Packages, systemPkg)

// Add document describes relationship
mergedDoc.Relationships = append(mergedDoc.Relationships, Relationship{
mergedDoc.Relationships = append(mergedDoc.Relationships, sbom.Relationship{
SPDXElementID: "SPDXRef-DOCUMENT",
RelatedSPDXElement: "SPDXRef-System",
RelationshipType: "DESCRIBES",
Expand All @@ -102,7 +104,7 @@ func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
mergedDoc.Packages = append(mergedDoc.Packages, pkg)

// Add relationship to system root
mergedDoc.Relationships = append(mergedDoc.Relationships, Relationship{
mergedDoc.Relationships = append(mergedDoc.Relationships, sbom.Relationship{
SPDXElementID: "SPDXRef-System",
RelatedSPDXElement: pkg.SPDXID,
RelationshipType: "CONTAINS",
Expand All @@ -127,7 +129,7 @@ func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
mergedDoc.Packages = append(mergedDoc.Packages, pkg)

// Add relationship to system root
mergedDoc.Relationships = append(mergedDoc.Relationships, Relationship{
mergedDoc.Relationships = append(mergedDoc.Relationships, sbom.Relationship{
SPDXElementID: "SPDXRef-System",
RelatedSPDXElement: pkg.SPDXID,
RelationshipType: "CONTAINS",
Expand All @@ -140,21 +142,21 @@ func (m *SBOMMerger) Merge(ubuntuPath, nixPath string) (*SPDXDocument, error) {
return mergedDoc, nil
}

func (m *SBOMMerger) loadDocument(path string) (*SPDXDocument, error) {
func (m *SBOMMerger) loadDocument(path string) (*sbom.SPDXDocument, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var doc SPDXDocument
var doc sbom.SPDXDocument
if err := json.Unmarshal(data, &doc); err != nil {
return nil, err
}

return &doc, nil
}

func (m *SBOMMerger) mergeCreators(ubuntuDoc, nixDoc *SPDXDocument) []string {
func (m *SBOMMerger) mergeCreators(ubuntuDoc, nixDoc *sbom.SPDXDocument) []string {
creatorMap := make(map[string]bool)
var creators []string

Expand Down Expand Up @@ -196,7 +198,7 @@ func (m *SBOMMerger) renumberSPDXID(originalID, prefix string) string {
return fmt.Sprintf("SPDXRef-%s-%s", prefix, strings.TrimPrefix(originalID, "SPDXRef-"))
}

func (m *SBOMMerger) Save(doc *SPDXDocument, outputPath string) error {
func (m *SBOMMerger) Save(doc *sbom.SPDXDocument, outputPath string) error {
file, err := os.Create(outputPath)
if err != nil {
return err
Expand Down
Loading
Loading