Skip to content

Commit

Permalink
allow first add statement in config files (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomoyamachi authored Jul 9, 2023
1 parent a8fc623 commit 53e0295
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 50 deletions.
149 changes: 101 additions & 48 deletions pkg/assessor/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,43 +66,74 @@ func compileSensitivePatterns() error {
return nil
}

type AssessmentWithColumns struct {
types.Assessment
HistoryIndex int
}

func checkAssessments(img types.Image) (assesses []*types.Assessment, err error) {
if err := compileSensitivePatterns(); err != nil {
return nil, err
}
if img.Config.User == "" || img.Config.User == "root" {
assesses = append(assesses, &types.Assessment{
Code: types.AvoidRootDefault,
Filename: ConfigFileName,
Desc: "Last user should not be root",
})
}

if img.Config.Healthcheck == nil {
assesses = append(assesses, &types.Assessment{
Code: types.AddHealthcheck,
Filename: ConfigFileName,
Desc: "not found HEALTHCHECK statement",
})
}

assessesCh := make(chan []*types.Assessment)
//assessesCh := make(chan []*types.Assessment)
assessesCh := make(chan []*AssessmentWithColumns)
for index, cmd := range img.History {
go func(index int, cmd types.History) {
assessesCh <- assessHistory(index, cmd)
}(index, cmd)
}

timeout := time.After(10 * time.Second)
assessWithColumns := []*AssessmentWithColumns{}
for i := 0; i < len(img.History); i++ {
select {
case results := <-assessesCh:
assesses = append(assesses, results...)
assessWithColumns = append(assessWithColumns, results...)
case <-timeout:
return nil, errors.New("timeout: manifest assessor")
}
}

sliceIndexShouldDelete := 0
historyIndexOfSmallestAddStatement := -1
assesses = make([]*types.Assessment, len(assessWithColumns))
for idx, assess := range assessWithColumns {
assesses[idx] = &assess.Assessment
if assess.Code == types.UseCOPY {
if historyIndexOfSmallestAddStatement == -1 {
historyIndexOfSmallestAddStatement = assess.HistoryIndex
sliceIndexShouldDelete = idx
} else if assess.HistoryIndex < historyIndexOfSmallestAddStatement {
historyIndexOfSmallestAddStatement = assess.HistoryIndex
sliceIndexShouldDelete = idx
}
}
}

// first ADD statement should not contain assessments
if historyIndexOfSmallestAddStatement != -1 {
assesses[sliceIndexShouldDelete] = assesses[len(assesses)-1]
assesses = assesses[:len(assesses)-1]
// fmt.Println("first ADD statement should not contain assessments", sliceIndexShouldDelete)
}

if img.Config.User == "" || img.Config.User == "root" {
assesses = append(assesses, &types.Assessment{
Code: types.AvoidRootDefault,
Filename: ConfigFileName,
Desc: "Last user should not be root",
})
}

if img.Config.Healthcheck == nil {
assesses = append(assesses, &types.Assessment{
Code: types.AddHealthcheck,
Filename: ConfigFileName,
Desc: "not found HEALTHCHECK statement",
})
}

for volume := range img.Config.Volumes {
if _, ok := sensitiveDirs[volume]; ok {
assesses = append(assesses, &types.Assessment{
Expand Down Expand Up @@ -134,62 +165,84 @@ func splitByCommands(line string) map[int][]string {
return tokens
}

func assessHistory(index int, cmd types.History) []*types.Assessment {
var assesses []*types.Assessment
func assessHistory(index int, cmd types.History) []*AssessmentWithColumns {
var assesses []*AssessmentWithColumns
cmdSlices := splitByCommands(cmd.CreatedBy)

found, varName := sensitiveVars(cmd.CreatedBy)
if found {
assesses = append(assesses, &types.Assessment{
Code: types.AvoidCredential,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Suspicious ENV key found : %s on %s (You can suppress it with --accept-key)", varName, cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.AvoidCredential,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Suspicious ENV key found : %s on %s (You can suppress it with --accept-key)", varName, cmd.CreatedBy),
},
HistoryIndex: index,
})
}
if reducableApkAdd(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.UseApkAddNoCache,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use --no-cache option if use 'apk add': %s", cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.UseApkAddNoCache,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use --no-cache option if use 'apk add': %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}

if reducableAptGetInstall(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.MinimizeAptGet,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use 'rm -rf /var/lib/apt/lists' after 'apt-get install|update' : %s", cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.MinimizeAptGet,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use 'rm -rf /var/lib/apt/lists' after 'apt-get install|update' : %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}

if reducableAptGetUpdate(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.UseAptGetUpdateNoCache,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Always combine 'apt-get update' with 'apt-get install|upgrade' : %s", cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.UseAptGetUpdateNoCache,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Always combine 'apt-get update' with 'apt-get install|upgrade' : %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}

if index != 0 && useADDstatement(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.UseCOPY,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use COPY : %s", cmd.CreatedBy),
// TODO: Allow the first ADD statement
if useADDstatement(cmdSlices) {
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.UseCOPY,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Use COPY : %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}

if useDistUpgrade(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.AvoidDistUpgrade,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Avoid dist-upgrade in container : %s", cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.AvoidDistUpgrade,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Avoid dist-upgrade in container : %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}
if useSudo(cmdSlices) {
assesses = append(assesses, &types.Assessment{
Code: types.AvoidSudo,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Avoid sudo in container : %s", cmd.CreatedBy),
assesses = append(assesses, &AssessmentWithColumns{
Assessment: types.Assessment{
Code: types.AvoidSudo,
Filename: ConfigFileName,
Desc: fmt.Sprintf("Avoid sudo in container : %s", cmd.CreatedBy),
},
HistoryIndex: index,
})
}

Expand Down
40 changes: 38 additions & 2 deletions pkg/assessor/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ func TestAssess(t *testing.T) {

assesses: []*types.Assessment{
{
Code: types.AvoidRootDefault,
Code: types.UseApkAddNoCache,
Filename: ConfigFileName,
},
{
Code: types.UseApkAddNoCache,
Code: types.AvoidRootDefault,
Filename: ConfigFileName,
},
{
Expand Down Expand Up @@ -69,6 +69,42 @@ func TestAssess(t *testing.T) {
},
},
},
"ADDStatementNotFirst": {
path: "./testdata/add_with_arg_statement.json",

assesses: []*types.Assessment{
{
Code: types.AvoidRootDefault,
Filename: ConfigFileName,
},
{
Code: types.AddHealthcheck,
Filename: ConfigFileName,
},
},
},
"MultiADDStatements": {
path: "./testdata/multi_add.json",

assesses: []*types.Assessment{
{
Code: types.AvoidRootDefault,
Filename: ConfigFileName,
},
{
Code: types.AddHealthcheck,
Filename: ConfigFileName,
},
{
Code: types.UseCOPY,
Filename: ConfigFileName,
},
{
Code: types.UseCOPY,
Filename: ConfigFileName,
},
},
},
}

for testname, v := range tests {
Expand Down
62 changes: 62 additions & 0 deletions pkg/assessor/manifest/testdata/add_with_arg_statement.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"architecture": "arm64",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"Labels": {
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "22.04"
},
"OnBuild": null
},
"created": "2023-07-06T14:45:51.166292881Z",
"history": [
{
"created": "2023-06-28T08:42:48.423710828Z",
"created_by": "/bin/sh -c #(nop) ARG RELEASE",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.499325674Z",
"created_by": "/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.566770422Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.653381453Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:50.199969775Z",
"created_by": "/bin/sh -c #(nop) ADD file:262490f82459c14632f5c9a6d6a5cf6c07b4f307e8fd380fa764662cda46e88f in / "
},
{
"created": "2023-06-28T08:42:50.42500211Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]",
"empty_layer": true
},
{
"created": "2023-07-06T14:45:51.166292881Z",
"created_by": "RUN /bin/sh -c echo \"hello\" # buildkit",
"comment": "buildkit.dockerfile.v0"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:c5ca84f245d30117a9a2720cb4297cedf3642816471d4d699f4d77e39e13a39c",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
]
},
"variant": "v8"
}
70 changes: 70 additions & 0 deletions pkg/assessor/manifest/testdata/multi_add.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"architecture": "arm64",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"Labels": {
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "22.04"
},
"OnBuild": null
},
"created": "2023-07-06T14:45:51.166292881Z",
"history": [
{
"created": "2023-06-28T08:42:48.423710828Z",
"created_by": "/bin/sh -c #(nop) ARG RELEASE",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.499325674Z",
"created_by": "/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.566770422Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:48.653381453Z",
"created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04",
"empty_layer": true
},
{
"created": "2023-06-28T08:42:50.199969775Z",
"created_by": "/bin/sh -c #(nop) ADD file:262490f82459c14632f5c9a6d6a5cf6c07b4f307e8fd380fa764662cda46e88f in / "
},
{
"created": "2023-06-28T08:42:50.42500211Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]",
"empty_layer": true
},
{
"created": "2023-07-06T14:45:51.166292881Z",
"created_by": "RUN /bin/sh -c echo \"hello\" # buildkit",
"comment": "buildkit.dockerfile.v0"
},
{
"created": "2023-06-28T08:42:50.199969775Z",
"created_by": "/bin/sh -c #(nop) ADD file:262490f82459c14632f5c9a6d6a5cf6c07b4f307e8fd380fa764662cda46e88f in / "
},
{
"created": "2023-06-28T08:42:50.199969775Z",
"created_by": "/bin/sh -c #(nop) ADD file:262490f82459c14632f5c9a6d6a5cf6c07b4f307e8fd380fa764662cda46e88f in / "
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:c5ca84f245d30117a9a2720cb4297cedf3642816471d4d699f4d77e39e13a39c",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
]
},
"variant": "v8"
}

0 comments on commit 53e0295

Please sign in to comment.