Skip to content

Commit

Permalink
fix(image): add logic to detect empty layers (aquasecurity#2790)
Browse files Browse the repository at this point in the history
* add logic to detect empty layers

* add test for createdBy from buildkit
  • Loading branch information
DmitriyLewen authored Aug 30, 2022
1 parent 9d018d4 commit 2473b2c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 1 deletion.
30 changes: 29 additions & 1 deletion pkg/fanal/image/daemon/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"
"os"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -153,12 +154,39 @@ func (img *image) configHistory() []v1.History {
},
CreatedBy: h.CreatedBy,
Comment: h.Comment,
EmptyLayer: h.Size == 0,
EmptyLayer: emptyLayer(h),
})
}
return history
}

func emptyLayer(history dimage.HistoryResponseItem) bool {
if history.Size != 0 {
return false
}
createdBy := strings.TrimSpace(strings.TrimLeft(history.CreatedBy, "/bin/sh -c #(nop)"))
// This logic is taken from https://github.com/moby/buildkit/blob/2942d13ff489a2a49082c99e6104517e357e53ad/frontend/dockerfile/dockerfile2llb/convert.go
if strings.HasPrefix(createdBy, "ENV") ||
strings.HasPrefix(createdBy, "MAINTAINER") ||
strings.HasPrefix(createdBy, "LABEL") ||
strings.HasPrefix(createdBy, "CMD") ||
strings.HasPrefix(createdBy, "ENTRYPOINT") ||
strings.HasPrefix(createdBy, "HEALTHCHECK") ||
strings.HasPrefix(createdBy, "EXPOSE") ||
strings.HasPrefix(createdBy, "USER") ||
strings.HasPrefix(createdBy, "VOLUME") ||
strings.HasPrefix(createdBy, "STOPSIGNAL") ||
strings.HasPrefix(createdBy, "SHELL") ||
strings.HasPrefix(createdBy, "ARG") ||
createdBy == "WORKDIR /" { // only when workdir == "/" then layer is empty
return true
}
// commands here: 'ADD', COPY, RUN and WORKDIR != "/"
// Also RUN command may not include 'RUN' prefix
// e.g. '/bin/sh -c mkdir test '
return false
}

func (img *image) diffIDs() ([]v1.Hash, error) {
var diffIDs []v1.Hash
for _, l := range img.inspect.RootFS.Layers {
Expand Down
74 changes: 74 additions & 0 deletions pkg/fanal/image/daemon/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

dimage "github.com/docker/docker/api/types/image"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -247,3 +248,76 @@ func Test_image_RawConfigFile(t *testing.T) {
})
}
}

func Test_image_emptyLayer(t *testing.T) {
tests := []struct {
name string
history dimage.HistoryResponseItem
want bool
}{
{
name: "size != 0",
history: dimage.HistoryResponseItem{
Size: 10,
},
want: false,
},
{
name: "ENV",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST",
},
want: true,
},
{
name: "ENV created with buildkit",
history: dimage.HistoryResponseItem{
CreatedBy: "ENV BUILDKIT_ENV=TEST",
Comment: "buildkit.dockerfile.v0",
},
want: true,
},
{
name: "ENV",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c #(nop) ENV TESTENV=TEST",
},
want: true,
},
{
name: "CMD",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
},
want: true,
},
{
name: "WORKDIR == '/'",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c #(nop) WORKDIR /",
},
want: true,
},
{
name: "WORKDIR != '/'",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c #(nop) WORKDIR /app",
},
want: false,
},
{
name: "without command",
history: dimage.HistoryResponseItem{
CreatedBy: "/bin/sh -c mkdir test",
},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
empty := emptyLayer(tt.history)
assert.Equal(t, tt.want, empty)
})
}
}

0 comments on commit 2473b2c

Please sign in to comment.