This repository has been archived by the owner on Jun 11, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
daemon.go
188 lines (156 loc) · 5.92 KB
/
daemon.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
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
)
// DaemonRequest is the request expected by the /v1/decrypt endpoint
type DaemonRequest struct {
AppID, AppVersion, TaskID string
// Secret encrypted with master key
RequestedSecret string
}
// DaemonResponse is the response returned by the /v1/decrypt endpoint
type DaemonResponse struct {
PlaintextSecret string
}
// DaemonStatusResponse is the response returned by the /v1/status endpoint
type DaemonStatusResponse struct {
Status string
}
func errorResponse(w http.ResponseWriter, r *http.Request, err interface{}, statusCode int) {
log.Printf("HTTP %d from %s: %s", statusCode, r.RemoteAddr, err)
http.Error(w, fmt.Sprintf("%s", err), statusCode)
}
func decryptRequest(app *MarathonApp, masterKey *[32]byte, serviceEnvelope string) (*DaemonRequest, error) {
// Authenticate with deploy key and decrypt
body, err := decryptEnvelope(app.DeployKey, masterKey, serviceEnvelope)
if err != nil {
return nil, fmt.Errorf("Failed to authenticate/decrypt request using deploy and master key (incorrect master key or hacking attempt? (%s))", err)
}
// Authenticate with optional service key and decrypt
if app.ServiceKey != nil {
body, err = decryptEnvelope(app.ServiceKey, masterKey, string(body))
if err != nil {
return nil, fmt.Errorf("Failed to authenticate/decrypt request using service and master key (incorrect master key or hacking attempt? (%s))", err)
}
}
// Unpack request struct
var request DaemonRequest
err = json.Unmarshal(body, &request)
if err != nil {
return nil, fmt.Errorf("Failed to parse JSON request (%s)", err)
}
// Validate that appId, appVersion, taskId corresponds to HTTP request params
if request.AppID != app.ID || request.AppVersion != app.Version || request.TaskID != app.TaskID {
return nil, errors.New("Given appid,appversion,taskid doesn't correspond to HTTP request params (bug or hacking attempt?)")
}
return &request, nil
}
func verifyAuthorization(app *MarathonApp, request *DaemonRequest) (bool, error) {
// Verify that encrypted string is present in app config
for _, value := range app.Env {
if strings.Contains(stripWhitespace(value), request.RequestedSecret) {
return true, nil
}
}
return false, errors.New("Given secret isn't part of app config (bug or hacking attempt?)")
}
func encryptResponse(app *MarathonApp, masterKey *[32]byte, plaintext []byte) ([]byte, error) {
message := DaemonResponse{PlaintextSecret: encode(plaintext)}
encoded, err := json.Marshal(message)
if err != nil {
return nil, err
}
// Encrypt with service key
response := string(encoded)
if app.ServiceKey != nil {
response, err = encryptEnvelope(app.ServiceKey, masterKey, []byte(response))
if err != nil {
return nil, err
}
}
// Encrypt with deploy key
encrypted, err := encryptEnvelope(app.DeployKey, masterKey, []byte(response))
if err != nil {
return nil, err
}
return []byte(encrypted), nil
}
func decryptEndpointHandler(marathonURL string, masterKey *[32]byte, strategy DecryptionStrategy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
errorResponse(w, r, "Expected POST method", http.StatusMethodNotAllowed)
return
}
err := r.ParseForm()
if err != nil {
errorResponse(w, r, "Expected application/x-www-form-urlencoded request body", http.StatusUnsupportedMediaType)
return
}
appID := r.Form.Get("appid")
appVersion := r.Form.Get("appversion")
taskID := r.Form.Get("taskid")
serviceEnvelope := r.Form.Get("envelope")
log.Printf("Received request from %s (%s, %s) at %s with envelope %s", appID, taskID, appVersion, r.RemoteAddr, ellipsis(serviceEnvelope, 64))
if appID == "" || taskID == "" || appVersion == "" || serviceEnvelope == "" {
errorResponse(w, r, errors.New("Expected parameters {appid, appversion, taskid, envelope}"), http.StatusBadRequest)
return
}
// Resolve app config version from Marathon
app, err := getMarathonApp(marathonURL, appID, appVersion, taskID)
if err != nil {
errorResponse(w, r, err, http.StatusInternalServerError)
return
}
// Authenticate and decrypt request
request, err := decryptRequest(app, masterKey, serviceEnvelope)
if err != nil {
errorResponse(w, r, err, http.StatusBadRequest)
return
}
// Verify that the secret is actually part of the service's config
ok, err := verifyAuthorization(app, request)
if !ok || err != nil {
errorResponse(w, r, err, http.StatusUnauthorized)
return
}
// Authenticate with config key and decrypt secret
plaintext, err := strategy.Decrypt(request.RequestedSecret)
if err != nil {
errorResponse(w, r, fmt.Errorf("Failed to decrypt plaintext secret, incorrect config or master key? (%s)", err), http.StatusBadRequest)
return
}
encrypted, err := encryptResponse(app, masterKey, plaintext)
if err != nil {
errorResponse(w, r, err, http.StatusInternalServerError)
return
}
w.Write([]byte(encrypted))
}
}
func statusEndpointHandler() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
message := DaemonStatusResponse{Status: "OK"}
encoded, err := json.Marshal(message)
if err != nil {
errorResponse(w, r, fmt.Errorf("Failed to serialize json response (%s)", err), http.StatusInternalServerError)
return
}
w.Write(encoded)
}
}
func daemonCommand(listenAddress string, marathonURL string, masterKey *[32]byte, tlsCertFile string, tlsKeyFile string, strategy DecryptionStrategy) {
http.HandleFunc("/v1/decrypt", decryptEndpointHandler(marathonURL, masterKey, strategy))
http.HandleFunc("/v1/status", statusEndpointHandler())
if tlsCertFile != "" && tlsKeyFile != "" {
log.Printf("Daemon listening on TLS %s", listenAddress)
log.Fatal(http.ListenAndServeTLS(listenAddress, tlsCertFile, tlsKeyFile, nil))
} else {
log.Printf("Daemon listening on %s", listenAddress)
log.Fatal(http.ListenAndServe(listenAddress, nil))
}
}