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

Add Trivy security scanner #21780

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eba262f
first pass at running Trivy on Docker targets
lilatomic Nov 7, 2024
c2c11c6
mv skip field
lilatomic Nov 14, 2024
2f8fda5
remove progress bars
lilatomic Nov 14, 2024
d429180
cache to Pants named caches
lilatomic Nov 14, 2024
52a96aa
tailor and lint
lilatomic Nov 14, 2024
1a6b7be
load config files
lilatomic Nov 14, 2024
d3b9f07
slight fix to docs on loading config files
lilatomic Nov 14, 2024
6b5455c
add option for setting allowed severity options
lilatomic Nov 14, 2024
dadd20e
fix skip field
lilatomic Nov 21, 2024
9e3dffb
allow passing in command-specific arguments
lilatomic Nov 24, 2024
6fbae7d
implement trivy for terraform
lilatomic Nov 24, 2024
76fd2b7
add vars-file handling for trivy for terraform
lilatomic Nov 25, 2024
903fdd5
trivy can lint helm deployments
lilatomic Nov 25, 2024
253d276
trivy can lint helm charts too
lilatomic Nov 27, 2024
eedb1a0
mv terraform_fieldset_to_init_request
lilatomic Nov 28, 2024
387da98
rebuild terraform to allow extending to modules
lilatomic Nov 28, 2024
a9f7aba
trivy can lint terraform modules
lilatomic Nov 28, 2024
8251952
register skip fields
lilatomic Nov 30, 2024
235eafd
use correct partitioner
lilatomic Dec 1, 2024
0d216ac
add test for trivy terraform
lilatomic Dec 4, 2024
0aa1e33
do not forward vars files when running on terraform modules
lilatomic Dec 4, 2024
5b84240
add tests for docker
lilatomic Dec 5, 2024
23bb781
rebuild trivy validation to use actual subcommand for parsing output
lilatomic Dec 9, 2024
282b1a8
add tests for trivy on helm
lilatomic Dec 9, 2024
4403b77
# This is a combination of 2 commits.
lilatomic Dec 18, 2024
b9a30f1
fix heading level hierarchy and typo
lilatomic Dec 19, 2024
0c3e0a3
improve assertions
lilatomic Dec 19, 2024
e108cb8
add to changelog
lilatomic Mar 9, 2025
7193bf0
mark trivy linter as experimental in all backends
lilatomic Dec 26, 2024
cacc326
fix typechecks
lilatomic Dec 26, 2024
dac097c
fix typo
lilatomic Jan 29, 2025
f2dc1da
increase timeout on test
lilatomic Jan 29, 2025
b1c7c70
new lints
lilatomic Mar 9, 2025
b9967f0
allow more errors than expected to prevent break when new ones are de…
lilatomic Mar 9, 2025
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
19 changes: 17 additions & 2 deletions docs/docs/docker/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -396,17 +396,32 @@ It can help to simulate a hermetic environment by using `env -i`. With credentia

:::

## Linting Dockerfiles with Hadolint
## Linting Dockerfiles

Pants can run [Hadolint](https://github.com/hadolint/hadolint) on your Dockerfiles to check for errors and mistakes:
Once Docker linting backends are enabled, lint Dockerfiles with

```
❯ pants lint src/docker/hw/Dockerfile
```


### Linting Dockerfiles with Hadolint

Pants can run [Hadolint](https://github.com/hadolint/hadolint) on your Dockerfiles to check for errors and mistakes.
This must first be enabled by activating the Hadolint backend:

```toml title="pants.toml"
[GLOBAL]
backend_packages = ["pants.backend.docker.lint.hadolint"]
```

### Linting Dockerfiles with Trivy

Pants can run [Trivy](https://github.com/aquasecurity/trivy) on your Dockerfiles to check for security vulnerabilities.
This must first be enabled by activating the Trivy backend:

```toml title="pants.toml"
[GLOBAL]
backend_packages = ["pants.backend.docker.lint.trivy"]
```

26 changes: 19 additions & 7 deletions docs/docs/helm/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ Created src/helm/example/BUILD:

If your workspace contains any Helm unit tests (under a `tests` folder), Pants will also idenfity them and create `helm_unittest_tests` targets for them. Additionally, if your unit tests also have snapshots (under a `tests/__snapshot__` folder), `tailor` will identify those files as test snapshots and will create `resources` targets for them. See "Snapshot testing" below for more info.

### Basic operations
## Basic operations

The given setup is enough to now do some common operations on our Helm chart source code.

#### Linting
### Linting

The Helm backend has an implementation of the Pants' `lint` goal which hooks it with the `helm lint` command:

Expand Down Expand Up @@ -100,7 +100,19 @@ Likewise, in a similar way you could enable strict linting globally and then cho

You can set the field `skip_lint=True` on each `helm_chart` target to avoid linting it.

#### Package
#### Trivy

Pants can run [Trivy](https://github.com/aquasecurity/trivy) on your Helm Charts and deployments. When run on a deployment, Pants run Trivy on the rendered Helm Chart with the specified values.

This must first be enabled by activating the Trivy backend:

```toml title="pants.toml"
[GLOBAL]
backend_packages = ["pants.backend.experimental.helm.lint.trivy"]
```

### Package
### Package

Packing helm charts is supported out of the box via the Pants' `package` goal. The final package will be saved as a `.tgz` file under the `dist` folder at your source root.

Expand Down Expand Up @@ -201,7 +213,7 @@ pants test ::
✓ testprojects/src/helm/example/tests/env-configmap_test.yaml succeeded in 0.75s.
```

#### Feeding additional files to unit tests
### Feeding additional files to unit tests

In some cases we may want our tests to have access to additional files which are not part of the chart. This can be achieved by setting a dependency between our unit test targets and a `resources` target as follows:

Expand Down Expand Up @@ -304,7 +316,7 @@ not affected by the source roots setting. When using `relocated_files`, the file
to the value set in the `dest` field.
:::

#### Snapshot testing
### Snapshot testing

Unit test snapshots are supported by Pants by wrapping the snapshots in resources targets, as shown in the previous section. Snapshot resources will be automatically inferred as dependencies of the tests where they reside, so there is no need to add a explicit `dependencies` relationship in your `helm_unittest_tests` targets.

Expand All @@ -318,7 +330,7 @@ This will generate test snapshots for tests that require them, with out-of-date

If new `__snapshot__` folders are created after running the `generate-snapshots` target, we recommend running the `tailor` goal again so that Pants can detect these new folders and create `resources` targets as appropriate.

#### Timeouts
### Timeouts

Pants can cancel tests that take too long, which is useful to prevent tests from hanging indefinitely.

Expand Down Expand Up @@ -352,7 +364,7 @@ If a target sets its `timeout` higher than `[test].timeout_maximum`, Pants will

Use the option `pants test --no-timeouts` to temporarily disable timeouts, e.g. when debugging.

#### Retries
### Retries

Pants can automatically retry failed tests. This can help keep your builds passing even with flaky tests, like integration tests.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/helm/kubeconform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Overview

The Helm backend has opt-in support for using [Kubeconform](https://github.com/yannh/kubeconform) as a validation tool for both Helm charts and deployments. This gives the extra confidence that the templates defined in the different charts are conformant to a Kubernetes version or specification, plus the addtional benefit of ensuring that their final version (when used in deployments) also meets that criteria.
The Helm backend has opt-in support for using [Kubeconform](https://github.com/yannh/kubeconform) as a validation tool for both Helm charts and deployments. This gives the extra confidence that the templates defined in the different charts are conformant to a Kubernetes version or specification, plus the additional benefit of ensuring that their final version (when used in deployments) also meets that criteria.

To enable the usage of Kubeconform, first we need to activate the relevant backend in `pants.toml`:

Expand Down
13 changes: 13 additions & 0 deletions docs/docs/terraform/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,16 @@ To run `terraform plan`, use the `--dry-run` flag of the `experimental-deploy` g
```
pants experimental-deploy --dry-run ::
```

### Linters

#### Trivy

Pants can run [Trivy](https://github.com/aquasecurity/trivy) on your Terraform modules and deployments. When run against deployments, the relevant vars files will be used. When run against modules, no vars files will be passed.

This must first be enabled by activating the Trivy backend:

```toml title="pants.toml"
[GLOBAL]
backend_packages = ["pants.backend.experimental.terraform.lint.trivy"]
```
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ At the bottom of your file, tell Pants about your rules:
def rules():
return [
*collect_rules(),
ShellcheckRequest.rules(partitioner_type=PartitionerType.DEFAULT_SINGLE_PARTITION),
*ShellcheckRequest.rules(),
UnionRule(ExportableTool, Shellcheck), # allows exporting the `shellcheck` binary
]
```
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/writing-plugins/common-subsystem-tasks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Only some language backends support `pants export`. These include the Python and
f"""
If true, Pants will include all relevant config files during runs.

Use `[{cls.options_scope}].config` and `[{cls.options_scope}].custom_check_dir` instead if your config is in a non-standard location.
Use `[{cls.options_scope}].config` instead if your config is in a non-standard location.
"""
),
)
Expand Down Expand Up @@ -161,7 +161,7 @@ Only some language backends support `pants export`. These include the Python and
)

args = []
if subsystem.config_request:
if subsystem.config:
args.append(f"--config-file={subsystem.config}")

# run your process with the digest and args
Expand Down
8 changes: 8 additions & 0 deletions docs/notes/2.26.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ To enable this option, add the following to your `pants.toml`
publish_noninteractively = true
```

Run [Trivy](https://github.com/aquasecurity/trivy) on Dockerfiles to scan for vulnerable packages.

#### Helm

Run [Trivy](https://github.com/aquasecurity/trivy) on Helm Charts and deployments to scan for misconfigurations.

#### Javascript

The Node.js runtime was upgraded from version [22.6.0](https://github.com/nodejs/node/releases/tag/v22.14.0) to version [22.14.0](https://github.com/nodejs/node/releases/tag/v22.14.0).
Expand Down Expand Up @@ -126,6 +132,8 @@ The `experiemental_test_shell_command` target type is no longer experimental and

For the `tfsec` linter, the deprecation of support for leading `v`s in the `version` and `known_versions` field has expired and been removed. Write `1.28.13` instead of `v1.28.13`.

Run [Trivy](https://github.com/aquasecurity/trivy) on Terraform modules and deployments to scan for misconfigurations.

#### TypeScript

Fixed a bug on dependency inference to correctly handle imports with file suffixes (e.g., `.generated.ts`).
Expand Down
8 changes: 8 additions & 0 deletions src/python/pants/backend/docker/lint/trivy/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
python_sources()

python_tests(
name="tests",
timeout=120,
)
Empty file.
88 changes: 88 additions & 0 deletions src/python/pants/backend/docker/lint/trivy/rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from dataclasses import dataclass
from typing import Any, cast

from pants.backend.docker.package_types import BuiltDockerImage
from pants.backend.docker.target_types import DockerImageSourceField, DockerImageTarget
from pants.backend.tools.trivy.rules import RunTrivyRequest, run_trivy
from pants.backend.tools.trivy.subsystem import SkipTrivyField, Trivy
from pants.core.goals.lint import LintResult, LintTargetsRequest
from pants.core.goals.package import BuiltPackage, EnvironmentAwarePackageRequest, PackageFieldSet
from pants.core.util_rules.partitions import PartitionerType
from pants.engine.addresses import Addresses
from pants.engine.internals.native_engine import EMPTY_DIGEST
from pants.engine.internals.selectors import Get
from pants.engine.rules import collect_rules, implicitly, rule
from pants.engine.target import (
FieldSet,
FieldSetsPerTarget,
FieldSetsPerTargetRequest,
Target,
Targets,
)
from pants.util.logging import LogLevel


@dataclass(frozen=True)
class TrivyDockerFieldSet(FieldSet):
required_fields = (DockerImageSourceField,)

source: DockerImageSourceField

@classmethod
def opt_out(cls, tgt: Target) -> bool:
return tgt.get(SkipTrivyField).value


class TrivyDockerRequest(LintTargetsRequest):
field_set_type = TrivyDockerFieldSet
tool_subsystem = Trivy
partitioner_type = PartitionerType.DEFAULT_ONE_PARTITION_PER_INPUT


def command_args():
return (
# workaround for Trivy DB being overloaded on pulls
"--db-repository",
"ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db",
# quiet progress output, which just clutters logs
"--no-progress",
)


@rule(desc="Lint Docker image with Trivy", level=LogLevel.DEBUG)
async def run_trivy_docker(
request: TrivyDockerRequest.Batch[TrivyDockerFieldSet, Any],
) -> LintResult:
addrs = tuple(e.address for e in request.elements)
tgts = await Get(Targets, Addresses(addrs))

field_sets_per_tgt = await Get(
FieldSetsPerTarget, FieldSetsPerTargetRequest(PackageFieldSet, tgts)
)
[field_set] = field_sets_per_tgt.field_sets

package = await Get(BuiltPackage, EnvironmentAwarePackageRequest(field_set))
built_image: BuiltDockerImage = cast(BuiltDockerImage, package.artifacts[0])
r = await run_trivy(
RunTrivyRequest(
command="image",
command_args=command_args(),
scanners=(),
target=built_image.image_id,
input_digest=EMPTY_DIGEST,
description=f"Run Trivy on docker image {','.join(built_image.tags)}",
),
**implicitly(),
)

return LintResult.create(request, r)


def rules():
return (
*collect_rules(),
*TrivyDockerRequest.rules(),
DockerImageTarget.register_plugin_field(SkipTrivyField),
)
100 changes: 100 additions & 0 deletions src/python/pants/backend/docker/lint/trivy/trivy_integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from textwrap import dedent

import pytest

from pants.backend.docker.lint.trivy.rules import TrivyDockerFieldSet, TrivyDockerRequest
from pants.backend.docker.lint.trivy.rules import rules as trivy_docker_rules
from pants.backend.docker.rules import rules as docker_rules
from pants.backend.docker.target_types import DockerImageTarget
from pants.backend.tools.trivy.rules import rules as trivy_rules
from pants.backend.tools.trivy.testutil import (
assert_trivy_output,
assert_trivy_success,
trivy_config,
)
from pants.core.goals import package
from pants.core.goals.lint import LintResult
from pants.core.util_rules import source_files
from pants.core.util_rules.partitions import PartitionMetadata
from pants.engine.internals.native_engine import Address
from pants.engine.rules import QueryRule
from pants.testutil.rule_runner import RuleRunner


@pytest.fixture
def rule_runner() -> RuleRunner:
from pants.core.goals.lint import LintResult
from pants.core.target_types import FileTarget

rule_runner = RuleRunner(
target_types=[DockerImageTarget, FileTarget],
rules=[
*trivy_docker_rules(),
*trivy_rules(),
*docker_rules(),
*package.rules(),
*source_files.rules(),
QueryRule(LintResult, [TrivyDockerRequest.Batch]),
],
)
rule_runner.write_files(
{
"Dockerfile.good": GOOD_FILE,
"Dockerfile.bad": BAD_FILE,
"file.txt": "",
"BUILD": dedent(
"""
file(name="file", source="file.txt")
docker_image(name="good", source="Dockerfile.good", dependencies=[":file"])
docker_image(name="bad", source="Dockerfile.bad")
"""
),
"trivy.yaml": trivy_config,
}
)
# DOCKER_HOST allows for humans with rootless docker to run docker-dependent tests
rule_runner.set_options(
("--trivy-extra-env-vars=DOCKER_HOST",),
env_inherit={"PATH", "DOCKER_HOST"},
)

return rule_runner


GOOD_FILE = "FROM scratch\nCOPY file.txt /" # A Docker image with nothing but a file is secure

BAD_FILE = (
"FROM alpine:3.14.9@sha256:fa26727c28837d1471c2f1524d297a0255c153b5d023d7badd1412be7e6e12a2"
)
BAD_IMAGE_TARGET = "sha256:9e02963d7df7e8da13c08d23fd2f09b9dcf779422151766a8963415994e74ae0 (alpine 3.14.9)" # this is Trivy's "Target" field


def test_trivy_good(rule_runner: RuleRunner) -> None:
tgt_good = rule_runner.get_target(Address("", target_name="good"))

result = rule_runner.request(
LintResult,
[
TrivyDockerRequest.Batch(
"trivy", (TrivyDockerFieldSet.create(tgt_good),), PartitionMetadata
)
],
)

assert_trivy_success(result)


def test_trivy_bad(rule_runner: RuleRunner) -> None:
tgt_bad = rule_runner.get_target(Address("", target_name="bad"))

result = rule_runner.request(
LintResult,
[
TrivyDockerRequest.Batch(
"trivy", (TrivyDockerFieldSet.create(tgt_bad),), PartitionMetadata
)
],
)
assert_trivy_output(result, 1, BAD_IMAGE_TARGET, "image", 4)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from pants.backend.docker.lint.trivy.rules import rules as trivy_docker_rules
from pants.backend.docker.rules import rules as docker_rules
from pants.backend.tools.trivy.rules import rules as trivy_rules


def rules():
return (
*docker_rules(),
*trivy_rules(),
*trivy_docker_rules(),
)
Empty file.
4 changes: 4 additions & 0 deletions src/python/pants/backend/experimental/helm/lint/trivy/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2024 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_sources()
Empty file.
Loading
Loading