Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(spdx): use the hasExtractedLicensingInfos field for licenses that are not listed in the SPDX #8077

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
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
41 changes: 41 additions & 0 deletions .github/workflows/spdx-cron.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: SPDX licenses cron
on:
schedule:
- cron: '0 0 * * 0' # every Sunday at 00:00
workflow_dispatch:

jobs:
build:
name: Check if SPDX exceptions
runs-on: ubuntu-24.04
steps:
- name: Check out code
uses: actions/[email protected]

- name: Check if SPDX exceptions are up-to-date
run: |
mage spdx:updateLicenseExceptions
if [ -n "$(git status --porcelain)" ]; then
echo "Run 'mage spdx:updateLicenseExceptions' and push it"
exit 1
fi

- name: Check if SPDX exceptions are up-to-date
run: |
mage spdx:updateLicenseExceptions
if [ -n "$(git status --porcelain)" ]; then
echo "Run 'mage spdx:updateLicenseExceptions' and push it"
exit 1
fi

- name: Microsoft Teams Notification
## Until the PR with the fix for the AdaptivCard version is merged yet
## https://github.com/Skitionek/notify-microsoft-teams/pull/96
## Use the aquasecurity fork
uses: aquasecurity/notify-microsoft-teams@master
if: failure()
with:
webhook_url: ${{ secrets.TRIVY_MSTEAMS_WEBHOOK }}
needs: ${{ toJson(needs) }}
job: ${{ toJson(job) }}
steps: ${{ toJson(steps) }}
7 changes: 7 additions & 0 deletions magefiles/magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,10 @@ type Helm mg.Namespace
func (Helm) UpdateVersion() error {
return sh.RunWith(ENV, "go", "run", "-tags=mage_helm", "./magefiles")
}

type SPDX mg.Namespace

// UpdateLicenseExceptions updates 'exception.json' with SPDX license exceptions
func (SPDX) UpdateLicenseExceptions() error {
return sh.RunWith(ENV, "go", "run", "-tags=mage_spdx", "./magefiles/spdx.go")
}
79 changes: 79 additions & 0 deletions magefiles/spdx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//go:build mage_spdx

package main

import (
"context"
"encoding/json"
"os"
"path/filepath"
"sort"
"strings"

"github.com/samber/lo"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/downloader"
"github.com/aquasecurity/trivy/pkg/log"
)

const (
exceptionFile = "exceptions.json"
exceptionDir = "./pkg/licensing/expression"
exceptionURL = "https://spdx.org/licenses/exceptions.json"
)

type Exceptions struct {
Exceptions []Exception `json:"exceptions"`
}

type Exception struct {
ID string `json:"licenseExceptionId"`
}

func main() {
if err := run(); err != nil {
log.Fatal("Fatal error", log.Err(err))
}

}

// run downloads exceptions.json file, takes only IDs and saves into `expression` package.
func run() error {
tmpDir, err := downloader.DownloadToTempDir(context.Background(), exceptionURL, downloader.Options{})
if err != nil {
return xerrors.Errorf("unable to download exceptions.json file: %w", err)
}
tmpFile, err := os.ReadFile(filepath.Join(tmpDir, exceptionFile))
if err != nil {
return xerrors.Errorf("unable to read exceptions.json file: %w", err)
}

exceptions := Exceptions{}
if err = json.Unmarshal(tmpFile, &exceptions); err != nil {
return xerrors.Errorf("unable to unmarshal exceptions.json file: %w", err)
}

exs := lo.Map(exceptions.Exceptions, func(ex Exception, _ int) string {
return strings.ToUpper(ex.ID)
})
sort.Strings(exs)

exceptionFile := filepath.Join(exceptionDir, exceptionFile)
f, err := os.Create(exceptionFile)
if err != nil {
return xerrors.Errorf("unable to create file %s: %w", exceptionFile, err)
}
defer f.Close()

e, err := json.Marshal(exs)
if err != nil {
return xerrors.Errorf("unable to marshal exceptions list: %w", err)
}

if _, err = f.Write(e); err != nil {
return xerrors.Errorf("unable to write exceptions list: %w", err)
}

return nil
}
88 changes: 88 additions & 0 deletions pkg/licensing/expression/category.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package expression

import (
"encoding/json"
"strings"
"sync"

"github.com/samber/lo"

"github.com/aquasecurity/trivy/pkg/log"

_ "embed"
)

// Canonical names of the licenses.
// ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L24-L177
const (
Expand Down Expand Up @@ -359,3 +371,79 @@ var (
ZeroBSD,
}
)

var spdxLicenses map[string]struct{}

var initSpdxLicenses = sync.OnceFunc(func() {
if len(spdxLicenses) > 0 {
return
}

licenseSlices := [][]string{
ForbiddenLicenses,
RestrictedLicenses,
ReciprocalLicenses,
NoticeLicenses,
PermissiveLicenses,
UnencumberedLicenses,
}

for _, licenseSlice := range licenseSlices {
spdxLicenses = lo.Assign(spdxLicenses, lo.SliceToMap(licenseSlice, func(l string) (string, struct{}) {
return l, struct{}{}
}))
}

// Save GNU licenses with "-or-later" and `"-only" suffixes
for _, l := range GnuLicenses {
license := SimpleExpr{
License: l,
}
spdxLicenses[license.String()] = struct{}{}

license.HasPlus = true
spdxLicenses[license.String()] = struct{}{}
}
})

//go:embed exceptions.json
var exceptions []byte

var spdxExceptions map[string]struct{}

var initSpdxExceptions = sync.OnceFunc(func() {
if len(spdxExceptions) > 0 {
return
}

var exs []string
if err := json.Unmarshal(exceptions, &exs); err != nil {
log.WithPrefix("SPDX").Warn("Unable to parse SPDX exception file", log.Err(err))
return
}

spdxExceptions = lo.SliceToMap(exs, func(e string) (string, struct{}) {
return e, struct{}{}
})
})

// ValidSpdxLicense returns true if SPDX license lists contain licenseID and license exception (if exists)
func ValidSpdxLicense(license string) bool {
if spdxLicenses == nil {
initSpdxLicenses()
}
if spdxExceptions == nil {
initSpdxExceptions()
}

id, exception, ok := strings.Cut(license, " WITH ")
if _, licenseIdFound := spdxLicenses[id]; licenseIdFound {
if !ok {
return true
}
if _, exceptionFound := spdxExceptions[strings.ToUpper(exception)]; exceptionFound {
return true
}
}
return false
}
1 change: 1 addition & 0 deletions pkg/licensing/expression/exceptions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["389-EXCEPTION","ASTERISK-EXCEPTION","ASTERISK-LINKING-PROTOCOLS-EXCEPTION","AUTOCONF-EXCEPTION-2.0","AUTOCONF-EXCEPTION-3.0","AUTOCONF-EXCEPTION-GENERIC","AUTOCONF-EXCEPTION-GENERIC-3.0","AUTOCONF-EXCEPTION-MACRO","BISON-EXCEPTION-1.24","BISON-EXCEPTION-2.2","BOOTLOADER-EXCEPTION","CLASSPATH-EXCEPTION-2.0","CLISP-EXCEPTION-2.0","CRYPTSETUP-OPENSSL-EXCEPTION","DIGIRULE-FOSS-EXCEPTION","ECOS-EXCEPTION-2.0","ERLANG-OTP-LINKING-EXCEPTION","FAWKES-RUNTIME-EXCEPTION","FLTK-EXCEPTION","FMT-EXCEPTION","FONT-EXCEPTION-2.0","FREERTOS-EXCEPTION-2.0","GCC-EXCEPTION-2.0","GCC-EXCEPTION-2.0-NOTE","GCC-EXCEPTION-3.1","GMSH-EXCEPTION","GNAT-EXCEPTION","GNOME-EXAMPLES-EXCEPTION","GNU-COMPILER-EXCEPTION","GNU-JAVAMAIL-EXCEPTION","GPL-3.0-INTERFACE-EXCEPTION","GPL-3.0-LINKING-EXCEPTION","GPL-3.0-LINKING-SOURCE-EXCEPTION","GPL-CC-1.0","GSTREAMER-EXCEPTION-2005","GSTREAMER-EXCEPTION-2008","I2P-GPL-JAVA-EXCEPTION","KICAD-LIBRARIES-EXCEPTION","LGPL-3.0-LINKING-EXCEPTION","LIBPRI-OPENH323-EXCEPTION","LIBTOOL-EXCEPTION","LINUX-SYSCALL-NOTE","LLGPL","LLVM-EXCEPTION","LZMA-EXCEPTION","MIF-EXCEPTION","NOKIA-QT-EXCEPTION-1.1","OCAML-LGPL-LINKING-EXCEPTION","OCCT-EXCEPTION-1.0","OPENJDK-ASSEMBLY-EXCEPTION-1.0","OPENVPN-OPENSSL-EXCEPTION","PCRE2-EXCEPTION","PS-OR-PDF-FONT-EXCEPTION-20170817","QPL-1.0-INRIA-2004-EXCEPTION","QT-GPL-EXCEPTION-1.0","QT-LGPL-EXCEPTION-1.1","QWT-EXCEPTION-1.0","ROMIC-EXCEPTION","RRDTOOL-FLOSS-EXCEPTION-2.0","SANE-EXCEPTION","SHL-2.0","SHL-2.1","STUNNEL-EXCEPTION","SWI-EXCEPTION","SWIFT-EXCEPTION","TEXINFO-EXCEPTION","U-BOOT-EXCEPTION-2.0","UBDL-EXCEPTION","UNIVERSAL-FOSS-EXCEPTION-1.0","VSFTPD-OPENSSL-EXCEPTION","WXWINDOWS-EXCEPTION-3.1","X11VNC-OPENSSL-EXCEPTION"]
Loading
Loading