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  }