-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
260 lines (214 loc) · 6.52 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package main
import (
"bufio"
"fmt"
"log"
"os"
"os/signal"
"strings"
"github.com/ChimeraCoder/anaconda"
"github.com/advanderveer/docksec/twitter"
"github.com/fsouza/go-dockerclient"
)
//
// Consider the example that busybox has a vulnerability
//
// 1. consumersa are running a countainer as such: `docker run -it -p 8080:80 jerbi/apache:1.0`
// 2. and have the docksec container running: `make build && make`
// 3. sycoso (re)tweets: 'CVE-2014-6271 in 9e1ed860cc088ae4b68ce28fb8888739652729e1107054f58dff90979f7dc935'
// 4. first time: running container should restart with latest pulled version
var client *docker.Client
const (
FILENAME = "images-cve.dat"
FORMAT = "%s@%s\r\n"
)
func markAsFixed(imageName string, cveName string) error {
f, err := os.OpenFile(FILENAME, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
if err != nil {
return err
}
defer f.Close()
imageAndCve := fmt.Sprintf(FORMAT, cveName, imageName)
_, err = f.WriteString(imageAndCve)
return err
}
func alreadyFixed(imageName string, cveName string) (bool, error) {
imageAndCve := fmt.Sprintf(FORMAT, cveName, imageName)
f, err := os.OpenFile(FILENAME, os.O_RDONLY|os.O_RDWR|os.O_CREATE, 0)
if err != nil {
fmt.Printf("failed opening %s", FILENAME)
return false, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
found := false
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == strings.TrimSpace(imageAndCve) {
fmt.Printf("found!")
found = true
break
}
}
return found, nil
}
func fix(vul *twitter.Vulnerable) error {
auth, err := docker.NewAuthConfigurations(strings.NewReader("{}"))
if err != nil {
return err
}
//fix by pulling latest and marking as such afterwards
for _, img := range vul.Images {
res, err := alreadyFixed(img.ID, vul.CVE)
if err != nil {
log.Printf("Error checking if the image %s is already fixed: %s", img.ID, err)
} else if res {
log.Printf("Already pulled latest tags for image '%s': %s", img.ID, err)
continue
}
for _, tag := range img.RepoTags {
//pull each latest for each tag
repo := tag[:strings.Index(tag, ":")]
opts := docker.PullImageOptions{
Repository: repo,
Tag: "latest",
}
log.Printf("Pulling '%s:latest'...", repo)
err := client.PullImage(opts, auth.Configs["https://index.docker.io/v1/"])
if err != nil {
log.Printf("Error pulling latest image: %s", err)
}
log.Printf("Done")
}
err = markAsFixed(img.ID, vul.CVE)
if err != nil {
log.Printf("Error marking img '%s' and cve '%s' as fixed: %s", img.ID, vul.CVE, err)
}
}
//restart each container with its newly pulled image and existing configs
//@TODO handle container names?
for _, apic := range vul.Containers {
c, err := client.InspectContainer(apic.ID)
if err != nil {
log.Printf("Error inspecting container '%s': %s", apic.ID, err)
}
hostConfig := c.HostConfig
if !c.State.Running {
continue
}
//@todo we assume we are looking to run the latest
c.Config.Image = c.Config.Image[:strings.Index(c.Config.Image, ":")] + ":latest"
newc, err := client.CreateContainer(docker.CreateContainerOptions{
Config: c.Config,
})
if err != nil {
log.Printf("Error creating new container of '%s': %s", c.ID, err)
break
}
//@todo this port change is actually domain logic
hostConfig.PortBindings["80/tcp"] = []docker.PortBinding{
docker.PortBinding{
HostPort: "8081",
},
}
err = client.StartContainer(newc.ID, hostConfig)
if err != nil {
log.Printf("Error start new container '%s': %s", newc.ID, err)
}
log.Printf("Created and started container of '%s': '%s', stopping old...", c.ID, newc.ID)
err = client.StopContainer(c.ID, 1)
if err != nil {
log.Printf("Failed to stop old container '%s': %s", c.ID, err)
}
}
return nil
}
func scan(infect_id string) (*twitter.Vulnerable, error) {
infect := &twitter.Vulnerable{"", []docker.APIContainers{}, []docker.APIImages{}}
imgs, err := client.ListImages(docker.ListImagesOptions{All: false})
if err != nil {
return infect, err
}
for _, img := range imgs {
himgs, _ := client.ImageHistory(img.ID)
for _, himg := range himgs {
if himg.ID == infect_id {
infect.Images = append(infect.Images, img)
}
}
}
containers, err := client.ListContainers(docker.ListContainersOptions{All: false})
if err != nil {
return infect, err
}
for _, container := range containers {
himgs, _ := client.ImageHistory(container.Image)
for _, himg := range himgs {
if himg.ID == infect_id {
infect.Containers = append(infect.Containers, container)
}
}
}
return infect, nil
}
func main() {
f, err := os.Create(FILENAME)
if err != nil {
log.Fatalf("failed to create file: %s", err)
}
f.Close()
client, err = docker.NewClient("unix:///var/run/docker.sock")
if err != nil {
log.Fatalf("Failed to connect to the docker daemon: %s", err)
}
tw, err := twitter.NewStream("advanderveer")
if err != nil {
if apiErr, ok := err.(*anaconda.ApiError); ok {
log.Printf("Api Error, headers: %s", apiErr.Header)
}
log.Fatalf("Failed to create twitter stream: %s", err)
}
defer tw.Close()
//handle signals
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt)
go func() {
<-sig
log.Println("Received interrupt signal, quitting twitter stream...")
tw.Stop()
}()
//listen for tweets
log.Printf("Starting twitter stream...")
evs := tw.Start()
for ev := range evs {
if ev.Type == twitter.EventNewVulnerability {
// run check images, eg 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55
log.Printf("Scanning Daemon for image '%s' (%s)...", ev.Image, ev.CVE)
res, err := scan(ev.Image)
if err != nil {
log.Fatalf("Failed to scan host: %s", err)
}
log.Printf("Done, Found vulnerabilities in %d images and %d containers!", len(res.Images), len(res.Containers))
//require: hostname, image name and container id
hostname, err := os.Hostname()
if err != nil {
log.Printf("Failed to determine hostname: %s", err)
}
if len(res.Images) > 0 || len(res.Containers) > 0 {
res.CVE = ev.CVE
err = tw.ReplyVulnerable(ev, hostname, res)
if err != nil {
log.Printf("Error replying vulnerable: %s", err)
}
}
log.Println("Sent reply")
} else if ev.Type == twitter.EventFixVulnerability {
vul := ev.Vulnerable
err := fix(vul)
if err != nil {
log.Printf("Failed to fix cve '%s' for %d images and %d containers", vul.CVE, len(vul.Images), len(vul.Containers))
}
log.Printf("Fixed cve '%s' for %d images and %d containers", vul.CVE, len(vul.Images), len(vul.Containers))
}
}
}