-
Notifications
You must be signed in to change notification settings - Fork 6
/
fakebuild.go
222 lines (188 loc) · 5.7 KB
/
fakebuild.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
// mkonion: create a Tor onion service for existing Docker containers
// Copyright (C) 2016 Aleksa Sarai <[email protected]>
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"text/template"
log "github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
containerTypes "github.com/docker/engine-api/types/container"
)
// Building a Docker image usually requires a real filesystem in order to create
// the tar archive sent to the Docker daemon. But if all you have a single
// configuration file, it's a pain to do that. In a sense, it's much nicer to
// embed all of the required information into the `mkonion` binary so we don't
// have to touch the filesystem.
const (
MkonionTag = "mkonion/tor:latest"
MkonionDockerfileTemplate = `
FROM alpine:3.4
RUN { \
echo '@edge http://dl-cdn.alpinelinux.org/alpine/edge/main'; \
echo '@edge http://dl-cdn.alpinelinux.org/alpine/edge/community';
} >> /etc/apk/repositories && \
apk update && \
apk upgrade && \
apk add \
tor@testing && \
{{ if .HasKey }}
mkdir -p /var/lib/tor/hidden_service && \
chmod 600 /var/lib/tor/hidden_service && \
{{ end }}
rm -rf /var/cache/apk/*
COPY torrc /etc/tor/torrc
{{ if .HasKey }}
COPY private_key /var/lib/tor/hidden_service/private_key
{{ end }}
ENTRYPOINT ["/usr/bin/tor", "-f", "/etc/tor/torrc"]
`
)
var dockerfileTemplate = template.Must(template.New("dockerfile").Parse(MkonionDockerfileTemplate))
func generateDockerfile(hasKey bool) (string, error) {
config := new(bytes.Buffer)
if err := dockerfileTemplate.Execute(config, struct {
HasKey bool
}{
HasKey: hasKey,
}); err != nil {
return "", err
}
return config.String(), nil
}
func makeBuildContext(torrc []byte, privatekey []byte) (io.Reader, error) {
hasKey := len(privatekey) > 0
dockerfile, err := generateDockerfile(hasKey)
if err != nil {
return nil, err
}
files := []*FakeFile{{
path: "torrc",
mode: 0644,
data: torrc,
}, {
path: "Dockerfile",
mode: 0644,
data: []byte(dockerfile),
}}
// XXX: This is probably slightly unsafe.
if hasKey {
files = append(files, &FakeFile{
path: "private_key",
mode: 0600,
data: privatekey,
})
}
return ArchiveContext(files)
}
func buildTorImage(cli *client.Client, ctx io.Reader) (string, error) {
// XXX: There's currently no way to get the image ID of a build without
// manually parsing the output, or tagging the image. Since I'm not in
// the mood for the former, we can tag the build with a random name.
// Unfortunately, untagging of images isn't supported, so we'll have to
// use a name that allows us to not pollute the host.
options := types.ImageBuildOptions{
// XXX: If we SuppressOutput we can get just the image ID, but we lose
// being able to tell users what the status of the build is.
//SuppressOutput: true,
Tags: []string{MkonionTag},
Remove: true,
ForceRemove: true,
Dockerfile: "Dockerfile",
Context: ctx,
}
build, err := cli.ImageBuild(options)
if err != nil {
return "", err
}
// XXX: For some weird reason, at this point the build has not finished. We
// need to wait for build.Body to be closed. We might as well tell the
// user what the status of the build is.
log.Infof("building %s", MkonionTag)
dec := json.NewDecoder(build.Body)
for {
// Modified from pkg/jsonmessage in Docker.
type JSONMessage struct {
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
}
// Decode the JSONMessages.
var jm JSONMessage
if err := dec.Decode(&jm); err != nil {
if err == io.EOF {
break
}
return "", err
}
jm.Stream = strings.TrimSpace(jm.Stream)
jm.Status = strings.TrimSpace(jm.Status)
// Log the status.
if jm.Stream != "" {
log.Info(jm.Stream)
}
if jm.Status != "" {
log.Info(jm.Status)
}
}
inspect, _, err := cli.ImageInspectWithRaw(MkonionTag, false)
if err != nil {
// XXX: Should probably clean up the built image here?
return "", err
}
log.Infof("successfully built %s image", MkonionTag)
return inspect.ID, nil
}
func runTorContainer(cli *client.Client, ident, imageID, network string) (string, error) {
config := &types.ContainerCreateConfig{
Name: ident,
Config: &containerTypes.Config{
Image: imageID,
},
}
resp, err := cli.ContainerCreate(config.Config, config.HostConfig, config.NetworkingConfig, config.Name)
if err != nil {
return "", err
}
// TODO: Remove container on failure.
for _, warning := range resp.Warnings {
log.Warn(warning)
}
if err := cli.ContainerStart(resp.ID); err != nil {
return "", err
}
// Connect to the network.
if err := cli.NetworkConnect(network, resp.ID, nil); err != nil {
return "", err
}
return resp.ID, err
}
type FakeBuildOptions struct {
ident string
networkID string
torrc []byte
privatekey []byte
}
// FakeBuildRun builds and starts a new mkonion tor server container entirely
// in memory with no files created on the local machine.
func FakeBuildRun(cli *client.Client, options *FakeBuildOptions) (string, error) {
ctx, err := makeBuildContext(options.torrc, options.privatekey)
if err != nil {
return "", fmt.Errorf("making build context: %s", err)
}
imageID, err := buildTorImage(cli, ctx)
if err != nil {
return "", fmt.Errorf("building image: %s", err)
}
containerID, err := runTorContainer(cli, options.ident, imageID, options.networkID)
if err != nil {
return "", fmt.Errorf("starting container: %s", err)
}
return containerID, nil
}