Enhance Grant Rule to Exclude Packages with "No License"
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.
+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!
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.
https://github.com/anchore/syft/pull/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!
π - 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
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"
Thank you, that looks promising. Once you have some code to try out the behavior, I would like to give it a try.
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.
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"]
Do you have a timeline when this feature will arive?
Sorry for the delay here @markussiebert - I went on an extended leave recently and have not been working on any anchore related code for the past 2 weeks. I'm back full time now and this is at the top of my list to get in. I would say look to this coming to land by the end of week or early next week. Again, sorry for the delay here π’
Hey @spiffcs, any updates on that, are you still busy with something else and do you have any estimate when it will be ready for use ?
@spiffcs sorry for tagging but we currently discuss on how we want to go further with grant. So is this bigger rewrite still planned or will it stay as it is because it would be too big of a change?
Hi @henrysachs - this is still planned but I've got some other work in front of it at the moment over in https://github.com/anchore/syft that helps detect golang licenses from source packages. When that's finished this is next on my list.
Hi @henrysachs - this is still planned but I've got some other work in front of it at the moment over in https://github.com/anchore/syft that helps detect golang licenses from source packages. When that's finished this is next on my list.
@spiffcs I assume you mean this pr: https://github.com/anchore/syft/pull/4127 congrats on getting it out!
I'm also interested in this feature. Is there any progress yet?
@mgmyannik check out the latest release of Grant - this functionality should be fulfilled now with the new config π