github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go (about) 1 package dockerfile 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 v1 "github.com/google/go-containerregistry/pkg/v1" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/devseccon/trivy/pkg/fanal/analyzer" 13 "github.com/devseccon/trivy/pkg/fanal/types" 14 ) 15 16 func Test_historyAnalyzer_Analyze(t *testing.T) { 17 tests := []struct { 18 name string 19 input analyzer.ConfigAnalysisInput 20 want *analyzer.ConfigAnalysisResult 21 wantErr bool 22 }{ 23 { 24 name: "happy", 25 input: analyzer.ConfigAnalysisInput{ 26 Config: &v1.ConfigFile{ 27 Config: v1.Config{ 28 Healthcheck: &v1.HealthConfig{ 29 Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, 30 Interval: time.Second * 10, 31 Timeout: time.Second * 3, 32 }, 33 }, 34 History: []v1.History{ 35 { 36 CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", 37 EmptyLayer: false, 38 }, 39 { 40 CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, 41 EmptyLayer: false, 42 }, 43 { 44 CreatedBy: `USER user`, 45 EmptyLayer: true, 46 }, 47 { 48 CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, 49 EmptyLayer: true, 50 }, 51 }, 52 }, 53 }, 54 want: &analyzer.ConfigAnalysisResult{ 55 Misconfiguration: &types.Misconfiguration{ 56 FileType: "dockerfile", 57 FilePath: "Dockerfile", 58 Failures: types.MisconfResults{ 59 types.MisconfResult{ 60 Namespace: "builtin.dockerfile.DS005", 61 Query: "data.builtin.dockerfile.DS005.deny", 62 Message: "Consider using 'COPY file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /' command instead of 'ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /'", 63 PolicyMetadata: types.PolicyMetadata{ 64 ID: "DS005", 65 AVDID: "AVD-DS-0005", 66 Type: "Dockerfile Security Check", 67 Title: "ADD instead of COPY", 68 Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", 69 Severity: "LOW", 70 RecommendedActions: "Use COPY instead of ADD", 71 References: []string{"https://docs.docker.com/engine/reference/builder/#add"}, 72 }, 73 CauseMetadata: types.CauseMetadata{ 74 Provider: "Dockerfile", 75 Service: "general", 76 StartLine: 1, 77 EndLine: 1, 78 Code: types.Code{ 79 Lines: []types.Line{ 80 { 81 Number: 1, 82 Content: "ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", 83 IsCause: true, 84 Truncated: false, 85 Highlighted: "\x1b[38;5;64mADD\x1b[0m file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", 86 FirstCause: true, 87 LastCause: true, 88 }, 89 }, 90 }, 91 }, 92 }, 93 }, 94 }, 95 }, 96 }, 97 { 98 name: "happy path. Base layer is found", 99 input: analyzer.ConfigAnalysisInput{ 100 Config: &v1.ConfigFile{ 101 Config: v1.Config{ 102 Healthcheck: &v1.HealthConfig{ 103 Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, 104 Interval: time.Second * 10, 105 Timeout: time.Second * 3, 106 }, 107 }, 108 History: []v1.History{ 109 { 110 CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", 111 EmptyLayer: false, 112 }, 113 { 114 CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, 115 EmptyLayer: true, 116 }, 117 { 118 CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, 119 EmptyLayer: false, 120 }, 121 { 122 CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, 123 EmptyLayer: true, 124 }, 125 }, 126 }, 127 }, 128 want: &analyzer.ConfigAnalysisResult{ 129 Misconfiguration: &types.Misconfiguration{ 130 FileType: "dockerfile", 131 FilePath: "Dockerfile", 132 Failures: types.MisconfResults{ 133 types.MisconfResult{ 134 Namespace: "builtin.dockerfile.DS002", 135 Query: "data.builtin.dockerfile.DS002.deny", 136 Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", 137 PolicyMetadata: types.PolicyMetadata{ 138 ID: "DS002", 139 AVDID: "AVD-DS-0002", 140 Type: "Dockerfile Security Check", 141 Title: "Image user should not be 'root'", 142 Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", 143 Severity: "HIGH", 144 RecommendedActions: "Add 'USER <non root user name>' line to the Dockerfile", 145 References: []string{ 146 "https://docs.docker." + 147 "com/develop/develop-images/dockerfile_best-practices/", 148 }, 149 }, 150 CauseMetadata: types.CauseMetadata{ 151 Provider: "Dockerfile", 152 Service: "general", 153 }, 154 }, 155 }, 156 }, 157 }, 158 }, 159 { 160 name: "nil config", 161 input: analyzer.ConfigAnalysisInput{ 162 Config: nil, 163 }, 164 }, 165 } 166 for _, tt := range tests { 167 t.Run(tt.name, func(t *testing.T) { 168 a, err := newHistoryAnalyzer(analyzer.ConfigAnalyzerOptions{}) 169 require.NoError(t, err) 170 got, err := a.Analyze(context.Background(), tt.input) 171 if tt.wantErr { 172 assert.Error(t, err) 173 return 174 } 175 if got != nil && got.Misconfiguration != nil { 176 got.Misconfiguration.Successes = nil // Not compare successes in this test 177 } 178 require.NoError(t, err) 179 assert.Equal(t, tt.want, got) 180 }) 181 } 182 }