Skip to content

Commit

Permalink
feat: add glob matching configuration (#4)
Browse files Browse the repository at this point in the history
feat: glob based matching for grant library
---------
Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs authored Nov 28, 2023
1 parent 5b29a72 commit be0c829
Show file tree
Hide file tree
Showing 20 changed files with 2,013 additions and 274 deletions.
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,40 @@ curl -sSfL https://raw.githubusercontent.com/anchore/grant/main/install.sh | sh

Grant can be used with any OCI image or sbom document to check for license compliance.

Matching Rules:
- Deny licenses take precedence over allow licenses
- Licenses are matched on a case-insensitive basis.
- If a license is in both lists it is denied.
- If a license is in neither list it is denied.

Supplied patterns follow a standard globbing syntax:
```
pattern:
{ term }
term:
`*` matches any sequence of non-separator characters
`**` matches any sequence of characters
`?` matches any single non-separator character
`[` [ `!` ] { character-range } `]`
character class (must be non-empty)
`{` pattern-list `}`
pattern alternatives
c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
`\` c matches character c
character-range:
c matches character c (c != `\\`, `-`, `]`)
`\` c matches character c
lo `-` hi matches character c for lo <= c <= hi
pattern-list:
pattern { `,` pattern }
comma-separated (without spaces) patterns
```

```bash

By default grant is configured to deny all licenses out of the box.


Expand All @@ -46,11 +80,10 @@ The following is an example of a `deny` oriented configuration which will deny `

```yaml
#.grant.yaml
precedence: [deny, allow]
deny: *
allow:
- MIT
- Apache-2
- Apache-*
```

If licenses are found that are not in the allow list, grant will return status code 1.
Expand Down
80 changes: 10 additions & 70 deletions cmd/grant/cli/command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@ package command

import (
"fmt"
"os"
"slices"

"github.com/jedib0t/go-pretty/v6/list"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grant/cmd/grant/cli/option"
"github.com/anchore/grant/grant"
"github.com/anchore/grant/internal/bus"
"github.com/anchore/grant/internal/input"
"github.com/anchore/grant/internal/log"
"github.com/anchore/syft/syft/format"
)

type CheckConfig struct {
Config string `json:"config" yaml:"config" mapstructure:"config"`
Format string `json:"format" yaml:"format" mapstructure:"format"`
option.Check `json:"" yaml:",inline" mapstructure:",squash"`
}

Expand Down Expand Up @@ -47,78 +44,21 @@ func Check(app clio.Application) *cobra.Command {
// TODO: upgrade the ui a bit with monitors for SBOM generation and license checking
// Progress can be incremented used on a per package basis when grant.Check is called
func runCheck(cfg CheckConfig, sources []string) (errs error) {
var reports []*grant.Report
// check if user provided source by stdin
// note: cat sbom.json | grant check spdx.json - is supported
// it will generate reports for both stdin and spdx.json
// it will generate results for both stdin and spdx.json
isStdin, _ := input.IsStdinPipeOrRedirect()
if isStdin && !slices.Contains(sources, "-") {
sources = append(sources, "-")
}

for _, src := range sources {
// TODO: branch into source detection here to generate the sbom
reader, err := input.GetReader(src)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not get reader for source: %s ", src))
}

sbomDecoders := format.NewDecoderCollection(format.Decoders()...)
sbom, formatID, version, err := sbomDecoders.Decode(reader)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not decode sbom: %s ", src))
}

log.Debugf("found sbom format: %s, version: %s; checking licenses...", formatID, version)
report := grant.NewReport(fmt.Sprintf("%s %s", sbom.Source.Name, sbom.Source.Version), cfg.Check)
for p := range sbom.Artifacts.Packages.Enumerate() {
log.Debugf("checking package: %s for non compliant licenses...", p.Name)
licenses := grant.ConvertSyftLicenses(p.Licenses)
report.Check(p.Name, licenses)
}
reports = append(reports, report)
}

// TODO: we need to return a non-zero exit code if any of the reports have a failure
// Reports should have a custom render function for the default command usage
// A machine readable json output should be available as well
return presentReports(reports)
}

func presentReports(reports []*grant.Report) error {
l := list.NewWriter() // TODO: style me
customStyle := list.Style{
Format: text.FormatTitle,
CharItemSingle: "",
CharItemTop: "",
CharItemFirst: "",
CharItemMiddle: "",
CharItemVertical: " ",
CharItemBottom: "",
CharNewline: "\n",
LinePrefix: "",
Name: "customStyle",
}
l.SetStyle(customStyle)
for _, report := range reports {
if len(report.PackageViolations) == 0 {
l.AppendItem("No License Violations: ✅")
continue
}

l.AppendItem("License Violations:")
for license, pkg := range report.LicenseViolations {
l.AppendItem(fmt.Sprintf("%s %s", fmt.Sprint("-"), license))
// TODO: we probably want a flag that can turn this on
for _, p := range pkg {
l.Indent()
l.AppendItem(fmt.Sprintf("%s %s", fmt.Sprint("-"), p))
l.UnIndent()
}
l.UnIndent()
}
policy, err := grant.NewPolicy(cfg.AllowLicenses, cfg.DenyLicenses)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not build policy from config: %s", cfg.Config))
}

bus.Report(l.Render())
return nil
// TODO: we need to support the ability to write the report to a file without redirecting stdout
return grant.NewReport(grant.Format(cfg.Format), policy, sources...).
Run().
Render(os.Stdout)
}
12 changes: 8 additions & 4 deletions cmd/grant/cli/option/check.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package option

import "github.com/anchore/grant/grant"

type Check struct {
Precidence []string `json:"precidence" yaml:"precidence" mapstructure:"precidence"`
DenyLicenses []string `json:"deny-licenses" yaml:"deny-licenses" mapstructure:"deny-licenses"`
AllowLicenses []string `json:"allow-licenses" yaml:"allow-licenses" mapstructure:"allow-licenses"`
DenyLicenses []string `json:"deny-licenses" yaml:"deny-licenses" mapstructure:"deny-licenses"`
}

func DefaultCheck() Check {
return Check{
Precidence: []string{"deny", "allow"},
DenyLicenses: []string{"*"},
AllowLicenses: []string{},
DenyLicenses: []string{"*"},
}
}

func (c Check) ToPolicy() (*grant.Policy, error) {
return grant.NewPolicy(c.AllowLicenses, c.DenyLicenses)
}
45 changes: 23 additions & 22 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
module github.com/anchore/grant

go 1.21.1
go 1.21.3

require (
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
github.com/anchore/bubbly v0.0.0-20231115205105-6542675d79fe
github.com/anchore/clio v0.0.0-20231128152715-767f62261f13
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
github.com/anchore/syft v0.96.0
github.com/anchore/syft v0.97.1
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.9.1
github.com/gkampitakis/go-snaps v0.4.11
github.com/github/go-spdx/v2 v2.2.0
github.com/gkampitakis/go-snaps v0.4.12
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jedib0t/go-pretty/v6 v6.4.9
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
Expand All @@ -33,7 +35,7 @@ require (
github.com/anchore/fangs v0.0.0-20230818131516-2186b10924fe // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect
github.com/anchore/stereoscope v0.0.0-20231027135531-5909e353ee88 // indirect
github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
Expand Down Expand Up @@ -61,16 +63,14 @@ require (
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
github.com/github/go-spdx/v2 v2.2.0 // indirect
github.com/gkampitakis/ciinfo v0.2.5 // indirect
github.com/gkampitakis/ciinfo v0.3.0 // indirect
github.com/gkampitakis/go-diff v1.3.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
github.com/google/uuid v1.4.0 // indirect
Expand All @@ -86,6 +86,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/maruel/natural v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
Expand Down Expand Up @@ -128,7 +129,7 @@ require (
github.com/sylabs/sif/v2 v2.11.5 // indirect
github.com/sylabs/squashfs v0.6.1 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/tidwall/gjson v1.16.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
Expand All @@ -139,18 +140,18 @@ require (
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
)
Loading

0 comments on commit be0c829

Please sign in to comment.