Skip to content

Commit e5d05a2

Browse files
authored
check permissions in any dir (#57)
* check permissions in any dir * check all setuid, setgid & fix test
1 parent 9f1048c commit e5d05a2

11 files changed

Lines changed: 79 additions & 61 deletions

File tree

CHECKPOINT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ HEALTHCHECK --interval=5m --timeout=3s \
7373
RUN apt-get update && apt-get install -y package-a
7474
```
7575

76-
### CIS-DI-0008: Remove `setuid` and `setgid` permissions in the images
76+
### CIS-DI-0008: Confirm safety of `setuid` and `setgid` files
7777

7878
> Removing `setuid` and `setgid` permissions in the images would prevent privilege escalation attacks in the containers.<br/>
7979
> `setuid` and `setgid` permissions could be used for elevating privileges.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ $ docker run --rm goodwithtech/dockle:v${DOCKLE_LATEST} [YOUR_IMAGE_NAME]
239239
| [CIS-DI-0005](CHECKPOINT.md#cis-di-0005-enable-content-trust-for-docker) | Enable Content trust for Docker | FATAL
240240
| [CIS-DI-0006](CHECKPOINT.md#cis-di-0006-add-healthcheck-instruction-to-the-container-image) | Add `HEALTHCHECK` instruction to the container image | WARN
241241
| [CIS-DI-0007](CHECKPOINT.md#cis-di-0007-do-not-use-update-instructions-alone-in-the-dockerfile) | Do not use `update` instructions alone in the Dockerfile | FATAL
242-
| [CIS-DI-0008](CHECKPOINT.md#cis-di-0008-remove-setuid-and-setgid-permissions-in-the-images) | Remove `setuid` and `setgid` permissions in the images | INFO
242+
| [CIS-DI-0008](CHECKPOINT.md#cis-di-0008-comfirm-safety-of-setuid-setgid-files) | Confirm safety of `setuid` and `setgid` files | INFO
243243
| [CIS-DI-0009](CHECKPOINT.md#cis-di-0009-use-copy-instead-of-add-in-dockerfile) | Use `COPY` instead of `ADD` in Dockerfile | FATAL
244244
| [CIS-DI-0010](CHECKPOINT.md#cis-di-0010-do-not-store-secrets-in-dockerfiles) | Do not store secrets in Dockerfiles | FATAL
245245
| [CIS-DI-0011](CHECKPOINT.md#cis-di-0011-install-verified-packages-only) | Install verified packages only | INFO

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
99
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
1010
github.com/genuinetools/reg v0.16.0
11-
github.com/goodwithtech/deckoder v0.0.0-20190914200440-2d3347be7dd3
11+
github.com/goodwithtech/deckoder v0.0.0-20191103063217-e4d89094c19d
1212
github.com/google/go-cmp v0.3.0
1313
github.com/moul/http2curl v1.0.0 // indirect
1414
github.com/parnurzeal/gorequest v0.2.15

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
8080
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
8181
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
8282
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
83-
github.com/goodwithtech/deckoder v0.0.0-20190914200440-2d3347be7dd3 h1:fHsxZkhj9PBDtNwo/6x+uOGe0TZajp/nVLFn7RgbVag=
84-
github.com/goodwithtech/deckoder v0.0.0-20190914200440-2d3347be7dd3/go.mod h1:+OL/nA/BaFd697EYdEP111Opjo26rf2a6CUtO671V8s=
83+
github.com/goodwithtech/deckoder v0.0.0-20191103063217-e4d89094c19d h1:T1racnAujPtKvGaE6HR2zHilu9P0/DkI34JAtZXgPcE=
84+
github.com/goodwithtech/deckoder v0.0.0-20191103063217-e4d89094c19d/go.mod h1:+OL/nA/BaFd697EYdEP111Opjo26rf2a6CUtO671V8s=
8585
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
8686
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
8787
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=

pkg/assessor/contentTrust/contentTrust.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/goodwithtech/dockle/pkg/types"
99
)
1010

11+
var HostEnvironmentFileName = "ENVIRONMENT variable on HOST OS"
12+
1113
type ContentTrustAssessor struct{}
1214

1315
func (a ContentTrustAssessor) Assess(fileMap extractor.FileMap) ([]*types.Assessment, error) {
@@ -17,7 +19,7 @@ func (a ContentTrustAssessor) Assess(fileMap extractor.FileMap) ([]*types.Assess
1719
return []*types.Assessment{
1820
{
1921
Type: types.UseContentTrust,
20-
Filename: "ENVIRONMENT variable",
22+
Filename: HostEnvironmentFileName,
2123
Desc: "export DOCKER_CONTENT_TRUST=1 before docker pull/build",
2224
},
2325
}, nil

pkg/assessor/manifest/manifest.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import (
1616

1717
type ManifestAssessor struct{}
1818

19-
var sensitiveDirs = map[string]struct{}{"/sys": {}, "/dev": {}, "/proc": {}}
20-
var suspiciousEnvKey = []string{"PASSWD", "PASSWORD", "SECRET", "KEY", "ACCESS"}
21-
var acceptanceEnvKey = map[string]struct{}{"GPG_KEY": {}, "GPG_KEYS": {}}
19+
var ConfigFileName = "metadata"
20+
var (
21+
sensitiveDirs = map[string]struct{}{"/sys": {}, "/dev": {}, "/proc": {}}
22+
suspiciousEnvKey = []string{"PASSWD", "PASSWORD", "SECRET", "KEY", "ACCESS"}
23+
acceptanceEnvKey = map[string]struct{}{"GPG_KEY": {}, "GPG_KEYS": {}}
24+
)
2225

2326
func (a ManifestAssessor) Assess(fileMap extractor.FileMap) (assesses []*types.Assessment, err error) {
2427
log.Logger.Debug("Scan start : config file")
@@ -41,7 +44,7 @@ func checkAssessments(img types.Image) (assesses []*types.Assessment, err error)
4144
if img.Config.User == "" || img.Config.User == "root" {
4245
assesses = append(assesses, &types.Assessment{
4346
Type: types.AvoidRootDefault,
44-
Filename: "docker config",
47+
Filename: ConfigFileName,
4548
Desc: "Last user should not be root",
4649
})
4750
}
@@ -56,7 +59,7 @@ func checkAssessments(img types.Image) (assesses []*types.Assessment, err error)
5659
}
5760
assesses = append(assesses, &types.Assessment{
5861
Type: types.AvoidEnvKeySecret,
59-
Filename: "docker config",
62+
Filename: ConfigFileName,
6063
Desc: fmt.Sprintf("Suspicious ENV key found : %s", envKey),
6164
})
6265
}
@@ -66,7 +69,7 @@ func checkAssessments(img types.Image) (assesses []*types.Assessment, err error)
6669
if img.Config.Healthcheck == nil {
6770
assesses = append(assesses, &types.Assessment{
6871
Type: types.AddHealthcheck,
69-
Filename: "docker config",
72+
Filename: ConfigFileName,
7073
Desc: "not found HEALTHCHECK statement",
7174
})
7275
}
@@ -92,7 +95,7 @@ func checkAssessments(img types.Image) (assesses []*types.Assessment, err error)
9295
if _, ok := sensitiveDirs[volume]; ok {
9396
assesses = append(assesses, &types.Assessment{
9497
Type: types.AvoidSensitiveDirectoryMounting,
95-
Filename: "docker config",
98+
Filename: ConfigFileName,
9699
Desc: fmt.Sprintf("Avoid mounting sensitive dirs : %s", volume),
97100
})
98101
}
@@ -125,46 +128,46 @@ func assessHistory(index int, cmd types.History) []*types.Assessment {
125128
if reducableApkAdd(cmdSlices) {
126129
assesses = append(assesses, &types.Assessment{
127130
Type: types.UseApkAddNoCache,
128-
Filename: "docker config",
131+
Filename: ConfigFileName,
129132
Desc: fmt.Sprintf("Use --no-cache option if use 'apk add': %s", cmd.CreatedBy),
130133
})
131134
}
132135

133136
if reducableAptGetInstall(cmdSlices) {
134137
assesses = append(assesses, &types.Assessment{
135138
Type: types.MinimizeAptGet,
136-
Filename: "docker config",
139+
Filename: ConfigFileName,
137140
Desc: fmt.Sprintf("Use 'rm -rf /var/lib/apt/lists' after 'apt-get install' : %s", cmd.CreatedBy),
138141
})
139142
}
140143

141144
if reducableAptGetUpdate(cmdSlices) {
142145
assesses = append(assesses, &types.Assessment{
143146
Type: types.UseAptGetUpdateNoCache,
144-
Filename: "docker config",
147+
Filename: ConfigFileName,
145148
Desc: fmt.Sprintf("Always combine 'apt-get update' with 'apt-get install' : %s", cmd.CreatedBy),
146149
})
147150
}
148151

149152
if index != 0 && useADDstatement(cmdSlices) {
150153
assesses = append(assesses, &types.Assessment{
151154
Type: types.UseCOPY,
152-
Filename: "docker config",
155+
Filename: ConfigFileName,
153156
Desc: fmt.Sprintf("Use COPY : %s", cmd.CreatedBy),
154157
})
155158
}
156159

157160
if useDistUpgrade(cmdSlices) {
158161
assesses = append(assesses, &types.Assessment{
159162
Type: types.AvoidDistUpgrade,
160-
Filename: "docker config",
163+
Filename: ConfigFileName,
161164
Desc: fmt.Sprintf("Avoid upgrade in container : %s", cmd.CreatedBy),
162165
})
163166
}
164167
if useSudo(cmdSlices) {
165168
assesses = append(assesses, &types.Assessment{
166169
Type: types.AvoidSudo,
167-
Filename: "docker config",
170+
Filename: ConfigFileName,
168171
Desc: fmt.Sprintf("Avoid sudo in container : %s", cmd.CreatedBy),
169172
})
170173
}

pkg/assessor/manifest/manifest_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ func TestAssess(t *testing.T) {
2121
assesses: []*types.Assessment{
2222
{
2323
Type: types.AvoidRootDefault,
24-
Filename: "docker config",
24+
Filename: ConfigFileName,
2525
},
2626
{
2727
Type: types.AddHealthcheck,
28-
Filename: "docker config",
28+
Filename: ConfigFileName,
2929
},
3030
},
3131
},
@@ -35,19 +35,19 @@ func TestAssess(t *testing.T) {
3535
assesses: []*types.Assessment{
3636
{
3737
Type: types.AvoidRootDefault,
38-
Filename: "docker config",
38+
Filename: ConfigFileName,
3939
},
4040
{
4141
Type: types.UseApkAddNoCache,
42-
Filename: "docker config",
42+
Filename: ConfigFileName,
4343
},
4444
{
4545
Type: types.AddHealthcheck,
46-
Filename: "docker config",
46+
Filename: ConfigFileName,
4747
},
4848
{
4949
Type: types.UseCOPY,
50-
Filename: "docker config",
50+
Filename: ConfigFileName,
5151
},
5252
},
5353
},

pkg/assessor/privilege/suid.go

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,45 @@ package privilege
33
import (
44
"fmt"
55
"os"
6-
"strings"
76

87
"github.com/goodwithtech/deckoder/extractor"
98
"github.com/goodwithtech/dockle/pkg/types"
109
)
1110

1211
type PrivilegeAssessor struct{}
1312

14-
var ignorePaths = []string{"bin/", "usr/lib/"}
15-
1613
func (a PrivilegeAssessor) Assess(fileMap extractor.FileMap) ([]*types.Assessment, error) {
1714
var assesses []*types.Assessment
1815

1916
for filename, filedata := range fileMap {
20-
if containIgnorePath(filename) {
21-
continue
22-
}
2317
if filedata.FileMode&os.ModeSetuid != 0 {
2418
assesses = append(
2519
assesses,
2620
&types.Assessment{
27-
Type: types.RemoveSetuidSetgid,
21+
Type: types.CheckSuidGuid,
2822
Filename: filename,
29-
Desc: fmt.Sprintf("Found setuid file: %s %s", filename, filedata.FileMode),
23+
Desc: fmt.Sprintf("setuid file: %s %s", filename, filedata.FileMode),
3024
})
3125
}
3226
if filedata.FileMode&os.ModeSetgid != 0 {
3327
assesses = append(
3428
assesses,
3529
&types.Assessment{
36-
Type: types.RemoveSetuidSetgid,
30+
Type: types.CheckSuidGuid,
3731
Filename: filename,
38-
Desc: fmt.Sprintf("Found setuid file: %s %s", filename, filedata.FileMode),
32+
Desc: fmt.Sprintf("setgid file: %s %s", filename, filedata.FileMode),
3933
})
4034
}
4135

4236
}
4337
return assesses, nil
4438
}
4539

46-
func containIgnorePath(filename string) bool {
47-
for _, ignoreDir := range ignorePaths {
48-
if strings.Contains(filename, ignoreDir) {
49-
return true
50-
}
51-
}
52-
return false
53-
}
54-
5540
func (a PrivilegeAssessor) RequiredFiles() []string {
5641
return []string{}
5742
}
5843

5944
//const GidMode os.FileMode = 4000
6045
func (a PrivilegeAssessor) RequiredPermissions() []os.FileMode {
61-
return []os.FileMode{os.ModeSocket, os.ModeSetuid}
46+
return []os.FileMode{os.ModeSetgid, os.ModeSetuid}
6247
}

pkg/scanner/scan_test.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/goodwithtech/dockle/pkg/assessor/contentTrust"
9+
10+
"github.com/goodwithtech/dockle/pkg/assessor/manifest"
11+
812
"github.com/google/go-cmp/cmp/cmpopts"
913

1014
deckodertypes "github.com/goodwithtech/deckoder/types"
@@ -24,19 +28,26 @@ func TestScanImage(t *testing.T) {
2428
wantErr error
2529
expected []*types.Assessment
2630
}{
27-
"test-image": {
28-
fileName: "",
29-
imageName: "goodwithtech/test-image:v1",
31+
"Dockerfile.base": {
32+
fileName: "",
33+
// testdata/Dockerfile.base
34+
imageName: "goodwithtech/dockle-test:base-test",
3035
option: deckodertypes.DockerOption{Timeout: time.Minute},
3136
expected: []*types.Assessment{
32-
{Type: types.AvoidEmptyPassword},
33-
{Type: types.AvoidRootDefault},
34-
{Type: types.AvoidCredentialFile},
35-
{Type: types.UseCOPY},
36-
{Type: types.AddHealthcheck},
37-
{Type: types.MinimizeAptGet},
38-
{Type: types.AvoidEnvKeySecret},
39-
{Type: types.UseContentTrust},
37+
{Type: types.AvoidEmptyPassword, Filename: "etc/shadow"},
38+
{Type: types.AvoidRootDefault, Filename: manifest.ConfigFileName},
39+
{Type: types.AvoidCredentialFile, Filename: "app/credentials.json"},
40+
{Type: types.CheckSuidGuid, Filename: "app/gid.txt"},
41+
{Type: types.CheckSuidGuid, Filename: "app/suid.txt"},
42+
{Type: types.CheckSuidGuid, Filename: "bin/mount"},
43+
{Type: types.CheckSuidGuid, Filename: "bin/su"},
44+
{Type: types.CheckSuidGuid, Filename: "bin/umount"},
45+
{Type: types.CheckSuidGuid, Filename: "usr/lib/openssh/ssh-keysign"},
46+
{Type: types.UseCOPY, Filename: manifest.ConfigFileName},
47+
{Type: types.AddHealthcheck, Filename: manifest.ConfigFileName},
48+
{Type: types.MinimizeAptGet, Filename: manifest.ConfigFileName},
49+
{Type: types.AvoidEnvKeySecret, Filename: manifest.ConfigFileName},
50+
{Type: types.UseContentTrust, Filename: contentTrust.HostEnvironmentFileName},
4051
},
4152
},
4253
"emptyArg": {
@@ -50,8 +61,13 @@ func TestScanImage(t *testing.T) {
5061
}
5162

5263
cmpopts := []cmp.Option{
53-
cmpopts.SortSlices(func(x, y *types.Assessment) bool { return x.Type < y.Type }),
54-
cmpopts.IgnoreTypes(""),
64+
cmpopts.SortSlices(func(x, y *types.Assessment) bool {
65+
if x.Type == y.Type {
66+
return x.Filename < y.Filename
67+
}
68+
return x.Type < y.Type
69+
}),
70+
cmpopts.IgnoreFields(types.Assessment{}, "Desc"),
5571
}
5672
if diff := cmp.Diff(assesses, v.expected, cmpopts...); diff != "" {
5773
t.Errorf("%s : tasks diff %v", name, diff)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM debian:jessie-slim
2+
3+
RUN apt-get update && apt-get install -y git
4+
RUN useradd nopasswd -p ""
5+
ADD credentials.json /app/credentials.json
6+
COPY suid.txt once-suid.txt gid.txt once-gid.txt /app/
7+
RUN chmod u+s /app/suid.txt /app/once-suid.txt && chmod g+s /app/gid.txt /app/once-gid.txt
8+
RUN chmod u-s /app/once-suid.txt && chmod g-s /app/once-gid.txt && echo "once" >> /app/once-suid.txt
9+
ENV MYSQL_PASSWD password
10+
RUN rm /sbin/unix_chkpwd /usr/bin/*
11+
VOLUME /usr

0 commit comments

Comments
 (0)