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

Enhance Grant Rule to Exclude Packages with "No License" #176

Open
markussiebert opened this issue Jan 31, 2025 · 7 comments
Open

Enhance Grant Rule to Exclude Packages with "No License" #176

markussiebert opened this issue Jan 31, 2025 · 7 comments
Assignees
Labels
enhancement Feature ehnancements

Comments

@markussiebert
Copy link

What would you like to be added:

It would be beneficial if the grant allowed defining a rule that matches "no license." Perhaps this should also be the default in the "deny all" rule.

Currently, there is the --show-packages option that lists the packages, but I haven’t found a way to exclude packages based on a specific rule.

Why is this needed:

At present, packages with "no license" slip through the "deny all" rule and I have not found a way to deny them with a custom rule. This might be intentional behavior, but it can be incorrect depending on definitions. "No license" can potentially mean that I am not permitted to use it. Currently, the grant does not recognize these cases.

@markussiebert markussiebert added the enhancement Feature ehnancements label Jan 31, 2025
@andrasszalai
Copy link

andrasszalai commented Feb 3, 2025

+1 vote for this. Thank you!

Some addition for this topic:
At the moment grant returns with exit code 0 even when packages without licensing information were found, so it does not allow to differentiate the "allowed packages" versus "allowed packages + no license found" cases.

In case there would be a possibility to catch the "no license" packages with the rules and deny them, the return code should be obviously non-zero.

Our use case:

We would like to use grant for a set of projects with a "central" config file containing the generic policies. This policy would deny packages with "no license found".

Since the projects differ significantly, we would like to use a local, project-specific config file for the exceptions, mainly for packages without licensing info (in-house libraries or else). Possibly managed by the developer teams.

The central and local config could be then merged during execution:
grant check --config "${GRANT_CUSTOM_CONFIG}" --config "${GRANT_CONFIG_NAME}" "${SBOM}"

We would really appreciate if this case could be covered as well. Thank you!

@spiffcs
Copy link
Collaborator

spiffcs commented Feb 3, 2025

Thanks @andrasszalai and @markussiebert - I'm currently over in Syft updating some of the license content options in the SBOM, but I'll try to carve out some time this week to get this enhancement in.

anchore/syft#3631

Current work is plumbing the Scanner into all of the catalogers so we can enhance the grant to recognize more licenses and return better results!

@spiffcs spiffcs moved this to In Progress in OSS Feb 7, 2025
@spiffcs spiffcs self-assigned this Feb 7, 2025
@spiffcs
Copy link
Collaborator

spiffcs commented Feb 10, 2025

👋 - just to let you guys know I didn't forget this one and have been working on it in the background:

Grant v0.3.0

When the request came in for excluding packages with no license it looked like a quick weekend task to add a top level config and get it all working. After thinking more about it and trying a few different designs grant has been given a rather large design upgrade going into v0.3.0.

Component Analysis, Constraints and New Rule Types

Rules can now use their glob to operate on components as well as licenses:

rules:
  # License Rule: Deny all GPL licenses
  - name: "Deny all Licenses"
    kind: "license" # ✅ Explicitly a LicenseRule
    reason: "Licenses are not permitted in this project unless configured with a specific allow rule"
    glob: "*"
    mode: "deny"
    severity: "high"

  #License Rule: Allow only permissive licenses for dependencies
  - name: "Allow Only Permissive Licenses for Dependencies"
    kind: "license"
    reason: "All licenses must be permissive licenses"
    glob: "*"
    mode: "allow"
    severity: "medium"
    constraints:
      - type: "license_group" # ✅ Uses LicenseGroupConstraint
        allowed_groups: ["Permissive"] # ❌ Deny copyleft/proprietary licenses

  # Component Rule: Deny components if unlicensed
  - name: "Deny any component if unlicensed"
    kind: "component" # ✅ Explicitly a ComponentRule
    reason: "Unlicensed components are not allowed when shipping to production"
    glob: "*"
    mode: "deny"
    severity: "critical"
    constraints:
      - type: "license_requirement" # ✅ Uses LicenseRequirementConstraint
        requirement: "none" # ❌ Deny only if component has NO license

  # Component Rule: Deny all outdated OpenSSL versions
  - name: "Deny OpenSSL <1.1.1"
    kind: "component"
    reason: "Older versions of OpenSSL are not secure"
    glob: "openssl"
    mode: "deny"
    severity: "critical"
    constraints:
      - type: "version" # ✅ Uses VersionConstraint
        constraint: ">=1.1.1 <2.0.0" # ❌ Deny anything outside this range

We've augmented rules to be able to pull from a short list of new optional constraints:

  • license_group <- license
  • version <- component
  • license_requirement <- component

In the above we have examples of each, but let's cover the example where we create a deny * for all components and add
a license requirement constraint. By adding the license_requirement constraint that says none the deny rule can express:
"Apply this deny * rule in cases where a component has no license"

Previous top level configs like CheckNonSPDX and OsiApproved have been moved into license rule constraints.

Grant API and Evaluations

With the new update grant Evaluations have been given a small update where they now include License and Component evaluations.
Users will note that Package => Component given the more generic nature of SBOM analysis. We originally defaulted to using package given that was the noun given to us by syft, but with the current state of SBOM doing static analysis on more things besides software packages the move to component while we can still break the API a bit seems waranted.

I'll post the branch tomorrow as I still have a bit of rebuilding to do after adding constraints onto rules at the core of the application. I think this kind of flexibility should help us extend the program moving forward as SBOM documents add more and more things they can catalog.

Notable Breaking changes:

  • Rules now need to specify if they are a component or license rule
  • Some top level configs have been deprecated in favor of related constraints
  • Packages rename => components

@spiffcs
Copy link
Collaborator

spiffcs commented Feb 10, 2025

I've iterated on the config a bit more to give us more options for the new behavior.

Adding this here to provide more options for ergonomics.

This new design achieves more simplicity in the config for rules. It also offers the ability to extend/inherit from an org wide policy. Finally it adds an enforce block which can be used globally for licenses/components or more specifically on rules to override global behavior.

Note the following examples can be inverted to switch the default behavior and specific lists as deny/allows

Simple cases

Simple: deny licenses while opening some gates for allow

Can be inverted to default allow with specific deny

policy:
  licenses:
    default_behavior: "deny"  # ❌ Deny all licenses unless explicitly allowed
    allow:
      - "MIT"  # ✅ Allow MIT license
      - "Apache-2.0"  # ✅ Allow Apache 2.0 license
      - "*BSD*"  # ✅ Allow all BSD-style licenses (e.g., BSD-2-Clause, BSD-3-Clause, etc.)

Simple: allow components while denying specific patterns and version constraints/ranges

Can be inverted to default deny with specific allow

policy:
  components:
    default_behavior: "allow"  # ✅ Allow all components unless explicitly denied
    deny:
      - "*log4j*"  # ❌ Deny all Log4j versions
      - "openssl<1.1.1"  # ❌ Deny OpenSSL if version is below 1.1.1
      - "nginx>=1.18.0,<1.20.0"  # ❌ Deny Nginx versions between 1.18.0 and 1.20.0

Combined with org wide inheritance

policy:
  extends: "org-policy.yaml"  # ✅ (Optional) Inherit from an organization-wide policy
  licenses:
    default_behavior: "deny"  # ❌ Deny all licenses unless explicitly allowed
    allow:
      - "MIT"  # ✅ Allow MIT license
      - "Apache-2.0"  # ✅ Allow Apache 2.0 license
      - "*BSD*"  # ✅ Allow all BSD-style licenses (e.g., BSD-2-Clause, BSD-3-Clause, etc.)
  components:
    default_behavior: "allow"  # ✅ Allow all components unless explicitly denied
    deny:
      - "*log4j*"  # ❌ Deny all Log4j versions
      - "openssl<1.1.1"  # ❌ Deny OpenSSL if version is below 1.1.1
      - "nginx>=1.18.0,<1.20.0"  # ❌ Deny Nginx versions **between 1.18.0 and 1.20.0**

Enforce

Adding an enforce block under licenses and components gives us the same behavior as constraint did above.

policy:
  components:
    enforce:
        license: true  # ✅ Require a license for all components (global)
policy:
  licenses:
    enforce:
      spdx: true # ✅ Require licenses to only be part of the official SPDX list
      osi: true # subset of SPDX are osi: y and can be optionally applied on top of SPDX

A small tension exists when we try to reconcile between the different default_behavior

Take the following example:

Enforce block for allow components, but require them to have a license

policy:
  components:
    enforce:
        license: true  # ✅ Require a license for all components (global)
    default_behavior: "allow" ✅ Allow all components outside of the enforce block requirements

Enforce block for allow components, but require them to have a license

policy:
  components:
    enforce:
        license: true  # ✅ Require a license for all components (global)
    default_behavior: "deny" ✅ Allow all components outside of the enforce block requirements

Here is a table that outlines what I interpret as the correct behavior where enforce is always used as a modifier AFTER the default is applied

Allow

Component Has License? Default Behavior Enforce Applied? Allowed / Denied?
log4j-core ✅ Yes 🟢 Allowed ✅ Passes Enforce 🟢 Allowed
log4j-core ❌ No 🟢 Allowed ❌ Fails Enforce 🔴 Denied
openssl ✅ Yes 🟢 Allowed ✅ Passes Enforce 🟢 Allowed
custom-tool ❌ No 🟢 Allowed ❌ Fails Enforce 🔴 Denied

Deny

Component Has License? Default Behavior Enforce Applied? Allowed / Denied?
log4j-core ✅ Yes 🔴 Denied ✅ Passes Enforce 🟢 Allowed
log4j-core ❌ No 🔴 Denied ❌ Fails Enforce 🔴 Denied
openssl ✅ Yes 🔴 Denied ✅ Passes Enforce 🟢 Allowed
custom-tool ❌ No 🔴 Denied ❌ Fails Enforce 🔴 Denied

Example where specific rules can make exceptions for the global

Enforce block with a deny defult; The allow list has an exception for a package we know has no license, but we want to pass

policy:
  components:
    enforce:
      license: true  # ✅ Require a license for all components (global)
    default_behavior: "deny" ✅ Allow all components outside of the enforce block requirements
    allow:
      - "*nginx*" <--- enforce will catch this and deny if it's unlicensed
      - pattern: "allowed-unlicense"
      	enforce:
          license: false

Example where different rules have just a pattern VS more complex fields

component:
  enforce:
    licensed: true  # ✅ Require component is licensed
    not_eol: true  # ✅ Require component is not eol
  default_behavior: "allow"
  deny:
    - "*log4j*"
    - pattern: "*wordpress*"
      severity: "critical"
      name: "we want to deny anything from wordpress"
      reason: "legal said no"

@markussiebert
Copy link
Author

Thank you, that looks promising. Once you have some code to try out the behavior, I would like to give it a try.

@LeonKolataAtGov
Copy link

We are currently introducing Grant, and I’d love to have this configuration flexibility! 👍 @spiffcs

I'm not entirely sure if your concept fully aligns with our needs since we have in-house libraries that are not licensed. It would be great to adjust the component definition to account for this.

components:
    allow: # ✅ Allow all components unless explicitly denied
      - "my-application"
      - "my-application-dependency"
    deny: # ❌ Deny named components
      - "something else" 

Rather than using a default_behavior for components, I’d prefer an explicit distinction between allow and deny. This way, I can configure named licenses within the policy structure while using the components section to explicitly define certain components.

@spiffcs
Copy link
Collaborator

spiffcs commented Feb 20, 2025

Code coming soon. We had some internal discussions about the config and came to a conclusion that now looks like this. I've had to do some big refactors around my original prototype branch, but code coming soon now!

policy:
  components:
    allow: all
    deny:
      - license: missing
      - "*log4j*"  # ❌ Deny all Log4j versions
      - name: "openssl"  # ❌ Deny OpenSSL if version is below 1.1.1
        version: "<1.1.1"
      - name: nginx  # ❌ Deny Nginx versions **between 1.18.0 and 1.20.0**
        version: ">=1.18.0,<1.20.0"
   ignore:
      - github.com/anchore/syft
 
  licenses:
    deny: all
    allow:
      - MIT
      - GPL-*
      - groups: ["permissive", "copyleft", "osi", "spdx-v3.2", "spdx"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature ehnancements
Projects
Status: In Progress
Development

No branches or pull requests

4 participants