Revert "Revert "Added support for scanning images from the docker daemon (#4276)" (#4291)"

This reverts commit 562dd7242b.
This commit is contained in:
Stephen Aghaulor
2025-07-11 15:10:42 -07:00
parent 55cfe6b18a
commit 053d6d8b70
4 changed files with 118 additions and 11 deletions
+8 -1
View File
@@ -280,7 +280,14 @@ trufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified,
Use the `--image` flag multiple times to scan multiple images.
```bash
# to scan from a remote registry
trufflehog docker --image trufflesecurity/secrets --results=verified,unknown
# to scan from the local docker daemon
trufflehog docker --image docker://new_image:tag --results=verified,unknown
# to scan from an image saved as a tarball
trufflehog docker --image file://path_to_image.tar --results=verified,unknown
```
## 12: Scan in CI
@@ -672,7 +679,7 @@ TruffleHog will send a JSON POST request containing the regex matches to a
configured webhook endpoint. If the endpoint responds with a `200 OK` response
status code, the secret is considered verified.
Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
and excluded word lists checked against the secret (captured group if present, entire match if capture group is not present). Note that if
your custom detector has multiple `regex` set (in this example `hogID`, and `hogToken`), then the filters get applied to each regex. [Here](examples/generic_with_filters.yml) is an example of a custom detector using these filters.
+1 -1
View File
@@ -184,7 +184,7 @@ var (
circleCiScanToken = circleCiScan.Flag("token", "CircleCI token. Can also be provided with environment variable").Envar("CIRCLECI_TOKEN").Required().String()
dockerScan = cli.Command("docker", "Scan Docker Image")
dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, otherwise a image registry is assumed.").Required().Strings()
dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, the docker:// prefix to point to the docker daemon, otherwise an image registry is assumed.").Required().Strings()
dockerScanToken = dockerScan.Flag("token", "Docker bearer token. Can also be provided with environment variable").Envar("DOCKER_TOKEN").String()
dockerExcludePaths = dockerScan.Flag("exclude-paths", "Comma separated list of paths to exclude from scan").String()
+37 -9
View File
@@ -13,6 +13,7 @@ import (
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
gzip "github.com/klauspost/pgzip"
@@ -179,8 +180,8 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
func (s *Source) processImage(ctx context.Context, image string) (imageInfo, error) {
var (
imgInfo imageInfo
hasDigest bool
imageName name.Reference
err error
)
remoteOpts, err := s.remoteOpts()
@@ -189,6 +190,7 @@ func (s *Source) processImage(ctx context.Context, image string) (imageInfo, err
}
const filePrefix = "file://"
const dockerPrefix = "docker://"
if strings.HasPrefix(image, filePrefix) {
image = strings.TrimPrefix(image, filePrefix)
imgInfo.base = image
@@ -196,18 +198,21 @@ func (s *Source) processImage(ctx context.Context, image string) (imageInfo, err
if err != nil {
return imgInfo, err
}
} else if strings.HasPrefix(image, dockerPrefix) {
image = strings.TrimPrefix(image, dockerPrefix)
imgInfo, imageName, err = s.extractImageNameTagDigest(image)
if err != nil {
return imgInfo, err
}
imgInfo.image, err = daemon.Image(imageName)
if err != nil {
return imgInfo, err
}
} else {
imgInfo.base, imgInfo.tag, hasDigest = baseAndTagFromImage(image)
if hasDigest {
imageName, err = name.NewDigest(image)
} else {
imageName, err = name.NewTag(image)
}
imgInfo, imageName, err = s.extractImageNameTagDigest(image)
if err != nil {
return imgInfo, err
}
imgInfo.image, err = remote.Image(imageName, remoteOpts...)
if err != nil {
return imgInfo, err
@@ -219,6 +224,29 @@ func (s *Source) processImage(ctx context.Context, image string) (imageInfo, err
return imgInfo, nil
}
// extractImageNameTagDigest parses the provided Docker image string and returns a name.Reference
// representing either the image's tag or digest, and any error encountered during parsing.
func (*Source) extractImageNameTagDigest(image string) (imageInfo, name.Reference, error) {
var (
hasDigest bool
imgInfo imageInfo
imgName name.Reference
err error
)
imgInfo.base, imgInfo.tag, hasDigest = baseAndTagFromImage(image)
if hasDigest {
imgName, err = name.NewDigest(image)
} else {
imgName, err = name.NewTag(image)
}
if err != nil {
return imgInfo, imgName, err
}
return imgInfo, imgName, nil
}
// getHistoryEntries collates an image's configuration history together with the
// corresponding layer digests for any non-empty layers.
func getHistoryEntries(ctx context.Context, imgInfo imageInfo, layers []v1.Layer) ([]historyEntryInfo, error) {
+72
View File
@@ -134,6 +134,78 @@ func TestDockerImageScanWithDigest(t *testing.T) {
assert.Equal(t, 1, historyCounter)
}
func TestDockerImageScanFromLocalDaemon(t *testing.T) {
dockerDaemonTestCases := []struct {
name string
image string
}{
{
name: "TestDockerImageScanFromLocalDaemon",
image: "docker://trufflesecurity/secrets",
},
{
name: "TestDockerImageScanFromLocalDaemonWithDigest",
image: "docker://trufflesecurity/secrets@sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11",
},
{
name: "TestDockerImageScanFromLocalDaemonWithTag",
image: "docker://trufflesecurity/secrets:latest",
},
}
for _, tt := range dockerDaemonTestCases {
t.Run(tt.name, func(t *testing.T) {
// This test assumes the local Docker daemon is running and the image exists locally.
// You may need to pull or build the image "trufflesecurity/secrets" locally before running this test.
dockerConn := &sourcespb.Docker{
Credential: &sourcespb.Docker_Unauthenticated{
Unauthenticated: &credentialspb.Unauthenticated{},
},
Images: []string{tt.image},
}
conn := &anypb.Any{}
err := conn.MarshalFrom(dockerConn)
assert.NoError(t, err)
s := &Source{}
err = s.Init(context.TODO(), "test source", 0, 0, false, conn, 1)
assert.NoError(t, err)
var wg sync.WaitGroup
chunksChan := make(chan *sources.Chunk, 1)
chunkCounter := 0
layerCounter := 0
historyCounter := 0
wg.Add(1)
go func() {
defer wg.Done()
for chunk := range chunksChan {
assert.NotEmpty(t, chunk)
chunkCounter++
if isHistoryChunk(t, chunk) {
historyCounter++
} else {
layerCounter++
}
}
}()
err = s.Chunks(context.TODO(), chunksChan)
assert.NoError(t, err)
close(chunksChan)
wg.Wait()
assert.Equal(t, 2, chunkCounter)
assert.Equal(t, 1, layerCounter)
assert.Equal(t, 1, historyCounter)
})
}
}
func TestBaseAndTagFromImage(t *testing.T) {
tests := []struct {
image string