Skip to content

Commit

Permalink
Merge pull request #40 from launchdarkly/behn/IDE-761/clone-dependenc…
Browse files Browse the repository at this point in the history
…y-scan-github

Clone dependency-scan action to public actions repo
  • Loading branch information
BehnH authored Sep 25, 2024
2 parents 6b4185e + f6e91c8 commit 08907ed
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
.idea
.vscode

**/.DS_Store

38 changes: 38 additions & 0 deletions actions/dependency-scan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# dependency-scan

This set of actions is used for license validation to prevent GPL licenses from being shipped in our product.

There are four sub-actions in this action.

## [generate-sbom](./generate-sbom)

This action generates a Software Bill of Materials (SBOM) for a project. It is uploaded as an artifact to the
GitHub Action workspace.

## [evaluate-policy](./evaluate-policy)

This action evaluates the SBOM against the policy file. A summary is posted to GitHub Actions.

# Example workflows

## Serial generation of BOMs

```yaml
name: Workflow
on: pull_request

jobs:
dependency-scan:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
- name: Generate SBOM
uses: launchdarkly/gh-actions/actions/dependency-scan/generate-sbom@main
with:
types: 'go,nodejs'
- name: Evaluate SBOM Policy
uses: launchdarkly/gh-actions/actions/dependency-scan/evaluate-policy@main
```
28 changes: 28 additions & 0 deletions actions/dependency-scan/evaluate-policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- action-docs-description source="action.yml" -->
## Description

Evaluate the OPA license policy against the BOM
<!-- action-docs-description source="action.yml" -->

<!-- action-docs-inputs source="action.yml" -->
## Inputs

| name | description | required | default |
| --- | --- | --- | --- |
| `policy-file` | <p>Filename of the license policy.</p> | `false` | `license_policy.rego` |
| `bom-file` | <p>File name for the Bill of Materials file</p> | `false` | `bom.json` |
| `opa-query` | <p>OPA query to use. If anything is returned, the build will fail.</p> | `false` | `data.launchdarkly.violation[x]` |
| `opa-version` | <p>The Open Policy Agent version to use.</p> | `false` | `latest` |
| `cyclonedx-version` | <p>The CycloneDX version to use.</p> | `false` | `latest` |
| `artifacts-pattern` | <p>Download one or more artifacts and merge them, prior to validation.</p> | `false` | `""` |
<!-- action-docs-inputs source="action.yml" -->

<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-runs source="action.yml" -->
## Runs

This action is a `composite` action.
<!-- action-docs-runs source="action.yml" -->
117 changes: 117 additions & 0 deletions actions/dependency-scan/evaluate-policy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Evaluate Policy
description: Evaluate the OPA license policy against the BOM

inputs:
policy-file:
description: Filename of the license policy.
required: false
default: license_policy.rego
bom-file:
description: File name for the Bill of Materials file
required: false
default: bom.json
opa-query:
description: OPA query to use. If anything is returned, the build will fail.
required: false
default: data.launchdarkly.violation[x]
opa-version:
description: The Open Policy Agent version to use.
required: false
default: latest
cyclonedx-version:
description: The CycloneDX version to use.
required: false
default: latest
artifacts-pattern:
description: Download one or more artifacts and merge them, prior to validation.
required: false

runs:
using: composite
steps:
- name: Open Policy Agent CLI version
id: versions
shell: bash
run: |
if [[ ${{ inputs.opa-version }} == latest ]]; then
echo "opa=$(curl -s https://api.github.com/repos/open-policy-agent/opa/releases/latest | jq -r .tag_name)" >> $GITHUB_OUTPUT
else
echo "opa=${{ inputs.opa-version }}" >> $GITHUB_OUTPUT
fi
if [[ "${{ inputs.artifacts-pattern }}" != "" && ${{ inputs.cyclonedx-version }} == latest ]]; then
echo "cyclonedx=$(curl -s https://api.github.com/repos/CycloneDX/cyclonedx-cli/releases/latest | jq -r .tag_name)" >> $GITHUB_OUTPUT
else
echo "cyclonedx=${{ inputs.cyclonedx-version }}" >> $GITHUB_OUTPUT
fi
- name: Cache Open Policy Agent
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
id: cache-opa
with:
path: /usr/local/bin/opa
key: opa-${{ steps.versions.outputs.opa }}

- name: Install Open Policy Agent
if: steps.cache-opa.outputs.cache-hit != 'true'
shell: bash
run: |
curl -L https://openpolicyagent.org/downloads/${{ steps.versions.outputs.opa }}/opa_linux_amd64 -o /usr/local/bin/opa
chmod +x /usr/local/bin/opa
- name: Cache CycloneDX CLI tool
if: inputs.artifacts-pattern != ''
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
id: cyclonedx-opa
with:
path: /usr/local/bin/cyclonedx
key: cyclonedx-${{ steps.versions.outputs.cyclonedx }}

- name: Install CycloneDX CLI tool
if: inputs.artifacts-pattern != '' && steps.cyclonedx-opa.outputs.cache-hit != 'true'
shell: bash
run: |
curl -L https://github.com/CycloneDX/cyclonedx-cli/releases/download/${{ steps.versions.outputs.cyclonedx }}/cyclonedx-linux-x64 -o /usr/local/bin/cyclonedx
chmod +x /usr/local/bin/cyclonedx
- name: Get all BOMs
if: inputs.artifacts-pattern != ''
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
path: .
pattern: ${{ inputs.artifacts-pattern }}

- name: Merge BOMs
if: inputs.artifacts-pattern != ''
run: cyclonedx merge --input-files ${{ inputs.artifacts-pattern }}/*.json --output-file ${{ inputs.bom-file }}
shell: bash

- name: Store Bill of Materials
if: inputs.artifacts-pattern != ''
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: merged-bom
path: ${{ inputs.bom-file }}
retention-days: 90

- name: Evaluate Policy
run: |
json_data="$(opa eval --fail-defined -i ${{ inputs.bom-file }} -d $GITHUB_ACTION_PATH/${{ inputs.policy-file }} '${{ inputs.opa-query }}' --format json)" || {
rc=$?
{
echo "Status: :x: failed"
echo ""
jq <<<"$json_data" -r '
[.result[].expressions[].value] | # Filter down to what we want
(.[0] | to_entries | map(.key)), # First element keys as header row
["---", "---"], # Markdown table header identifier
(.[] | to_entries | map(.value)) | # Finally get the real values
join(" | ") # join by "|" (to mark columns)
'
} >> $GITHUB_STEP_SUMMARY
printf "%s\n" "$json_data"
exit $rc
}
echo "Status: :white_check_mark: success" >> $GITHUB_STEP_SUMMARY
shell: bash
21 changes: 21 additions & 0 deletions actions/dependency-scan/evaluate-policy/license_policy.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package launchdarkly

default allow = false # unless otherwise defined, allow is false

allow = true { # allow is true if...
count(violation) == 0 # there are zero violations.
}

# The golang cyclonedx tool puts licenses under 'evidence'
violation[component["bom-ref"]] = {"dependency": component["bom-ref"], "license": license} {
component := input.components[_]
license := component.evidence.licenses[_].license.id
contains(license, "GPL") # should catch GPL, LGPL, AGPL, etc
}

# The Node cyclonedx tool puts licenses directly under the component
violation[component["bom-ref"]] = {"dependency": component["bom-ref"], "license": license} {
component := input.components[_]
license := component.licenses[_].license.id
contains(license, "GPL") # should catch GPL, LGPL, AGPL, etc
}
27 changes: 27 additions & 0 deletions actions/dependency-scan/generate-sbom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!-- action-docs-description source="action.yml" -->
## Description

Generate a Software Bill of Materials (SBOM)
<!-- action-docs-description source="action.yml" -->

<!-- action-docs-inputs source="action.yml" -->
## Inputs

| name | description | required | default |
| --- | --- | --- | --- |
| `types` | <p>Comma separated project types. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supported languages/platforms.</p> | `true` | `""` |
| `cdxgen-version` | <p>Version of cdxgen to use</p> | `false` | `10.9.2` |
| `project-directory` | <p>Relative path (from root of repo) to the root of the golang project / module</p> | `false` | `.` |
| `fetch-license` | <p>Fetch license information for dependencies</p> | `false` | `true` |
| `recurse` | <p>Recurse mode suitable for mono-repos.</p> | `false` | `true` |
<!-- action-docs-inputs source="action.yml" -->

<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-outputs source="action.yml" -->

<!-- action-docs-runs source="action.yml" -->
## Runs

This action is a `composite` action.
<!-- action-docs-runs source="action.yml" -->
68 changes: 68 additions & 0 deletions actions/dependency-scan/generate-sbom/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Generate SBOM
description: Generate a Software Bill of Materials (SBOM)

inputs:
types:
description: 'Comma separated project types. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supported languages/platforms.'
required: true
cdxgen-version:
description: 'Version of cdxgen to use'
required: false
default: '10.9.2'
project-directory:
description: 'Relative path (from root of repo) to the root of the golang project / module'
required: false
default: '.'
fetch-license:
description: 'Fetch license information for dependencies'
required: false
default: 'true'
recurse:
description: 'Recurse mode suitable for mono-repos.'
required: false
default: 'true'

runs:
using: composite

steps:
- name: Setup Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 20.x

- name: Install cdxgen
run: npm list -g | grep -qF @cyclonedx/cdxgen@${{ inputs.cdxgen-version }} || npm install -g @cyclonedx/cdxgen@${{ inputs.cdxgen-version }}
shell: bash

- name: Get information
id: info
shell: bash
run: |
if [[ "${{ inputs.types }}" == *,* ]]; then
echo "artifact_name=bom" >> "$GITHUB_OUTPUT"
echo "bom_file=bom.json" >> "$GITHUB_OUTPUT"
else
echo "artifact_name=bom-${{ inputs.types }}" >> "$GITHUB_OUTPUT"
echo "bom_file=bom.${{ inputs.types }}.json" >> "$GITHUB_OUTPUT"
fi
- name: Generate Bill of Materials
shell: bash
run: |
IFS=, read -ra types <<<"${{ inputs.types }}"
args=()
for type in "${types[@]}"; do
args+=(-t "$type")
done
[[ "${{ inputs.recurse }}" != true ]] && args+=(--no-recurse)
cdxgen "${args[@]}" -o "${{ steps.info.outputs.bom_file }}" ${{ inputs.project-directory }}
env:
FETCH_LICENSE: ${{ inputs.fetch-license }}

- name: Store Bill of Materials
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: ${{ steps.info.outputs.artifact_name }}
path: ${{ steps.info.outputs.bom_file }}
retention-days: 1

0 comments on commit 08907ed

Please sign in to comment.