forked from ANSSI-FR/transdep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebserver.go
303 lines (269 loc) · 9.58 KB
/
webserver.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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"
"github.com/ANSSI-FR/transdep/dependency"
"github.com/ANSSI-FR/transdep/graph"
dependency2 "github.com/ANSSI-FR/transdep/messages/dependency"
"github.com/ANSSI-FR/transdep/tools"
"strconv"
"time"
)
// handleRequest is common to all request handlers. The difference lies in the reqConf parameter whose value varies
// depending on the request handler.
func handleRequest(
params url.Values, reqConf *tools.RequestConfig, reqChan chan<- *dependency2.Request,
w http.ResponseWriter, req *http.Request,
) {
// Get requested domain
domain, ok := params["domain"]
if !ok || len(domain) != 1 {
w.WriteHeader(http.StatusBadRequest)
return
}
// Submit the request
depReq := dependency2.NewRequest(domain[0], true, false, reqConf.Exceptions)
select {
case <-tools.StartTimeout(20 * time.Second):
w.WriteHeader(http.StatusRequestTimeout)
return
case reqChan <- depReq:
res, err := depReq.Result()
if err != nil {
bstr, err := json.Marshal(err)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Add("content-type", "application/json+error")
w.Write(bstr)
return
}
rootNode, ok := res.(*graph.RelationshipNode)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
return
}
var queryResult *graph.WorkerAnalysisResult
allNamesResult, allNamesNo4Result, allNamesNo6Result, dnssecResult, dnssecNo4Result, dnssecNo6Result :=
graph.PerformAnalyseOnResult(rootNode, reqConf, nil)
if reqConf.AnalysisCond.DNSSEC == false {
if reqConf.AnalysisCond.NoV4 {
queryResult = allNamesNo4Result
} else if reqConf.AnalysisCond.NoV6 {
queryResult = allNamesNo6Result
} else {
queryResult = allNamesResult
}
} else {
if reqConf.AnalysisCond.NoV4 {
queryResult = dnssecNo4Result
} else if reqConf.AnalysisCond.NoV6 {
queryResult = dnssecNo6Result
} else {
queryResult = dnssecResult
}
}
if queryResult.Err != nil {
bstr, jsonErr := json.Marshal(queryResult.Err)
if jsonErr != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Add("content-type", "application/json+error")
w.Write(bstr)
return
}
bstr, jsonErr := json.Marshal(queryResult.Nodes)
if jsonErr != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Add("content-type", "application/json+nodes")
w.Write(bstr)
return
}
}
// getRequestConf uses the parameters from the query string to define the request configuration, notably concerning
// deemed acceptable DNS violations
func getRequestConf(params url.Values) *tools.RequestConfig {
var RFC8020, AcceptServfail bool
paramRFC8020, ok := params["rfc8020"]
if !ok || len(paramRFC8020) != 1 {
RFC8020 = false
} else if i, err := strconv.ParseInt(paramRFC8020[0], 10, 0); err != nil {
RFC8020 = false
} else {
RFC8020 = i != 0
}
paramAcceptServfail, ok := params["servfail"]
if !ok || len(paramAcceptServfail) != 1 {
AcceptServfail = false
} else if i, err := strconv.ParseInt(paramAcceptServfail[0], 10, 0); err != nil {
AcceptServfail = false
} else {
AcceptServfail = i != 0
}
// Prepare request-specific configuration based on rfc8020 and servfail query string parameters presence and value
reqConf := &tools.RequestConfig{
AnalysisCond: tools.AnalysisConditions{
All: false,
DNSSEC: false,
NoV4: false,
NoV6: false,
},
Exceptions: tools.Exceptions{
RFC8020: RFC8020,
AcceptServFailAsNoData: AcceptServfail,
},
}
return reqConf
}
func handleAllNamesRequests(reqChan chan<- *dependency2.Request, w http.ResponseWriter, req *http.Request) {
params, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Prepare request-specific configuration
reqConf := getRequestConf(params)
handleRequest(params, reqConf, reqChan, w, req)
}
func handleDNSSECRequests(reqChan chan<- *dependency2.Request, w http.ResponseWriter, req *http.Request) {
params, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Prepare request-specific configuration
reqConf := getRequestConf(params)
reqConf.AnalysisCond.DNSSEC = true
handleRequest(params, reqConf, reqChan, w, req)
}
func handleNo4Requests(reqChan chan<- *dependency2.Request, w http.ResponseWriter, req *http.Request) {
params, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Prepare request-specific configuration
reqConf := getRequestConf(params)
reqConf.AnalysisCond.NoV4 = true
handleRequest(params, reqConf, reqChan, w, req)
}
func handleNo6Requests(reqChan chan<- *dependency2.Request, w http.ResponseWriter, req *http.Request) {
params, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Prepare request-specific configuration
reqConf := getRequestConf(params)
reqConf.AnalysisCond.NoV6 = true
handleRequest(params, reqConf, reqChan, w, req)
}
func stopFinder(df *dependency.Finder, reqChan chan<- *dependency2.Request, secret string, w http.ResponseWriter, req *http.Request) {
params, err := url.ParseQuery(req.URL.RawQuery)
if err == nil {
if secretParam, ok := params["secret"]; ok && len(secretParam) == 1 && secretParam[0] == secret {
// Secret is correct, initiating graceful stop
go func() {
// Wait for 1 second, just to give the time to send the web confirmation page
time.Sleep(1 * time.Second)
fmt.Printf("Stopping the finder: ")
// Will print dots until os.Exit kills the process
go func() {
for {
fmt.Printf(".")
time.Sleep(100 * time.Millisecond)
}
}()
close(reqChan)
// Perform a graceful stop of the dependency finder, which will flush caches on disk
df.Stop()
fmt.Printf("OK\n")
os.Exit(0)
}()
// Returns a webpage confirming shutdown
w.WriteHeader(http.StatusOK)
buf := new(bytes.Buffer)
buf.WriteString("Stopping.")
w.Write(buf.Bytes())
return
}
}
// Reject all requests that are missing the secret parameter or whose secret value is different from the "secret"
// function parameter.
w.WriteHeader(http.StatusForbidden)
}
// runWebWorker is a go routine that handles dependency requests received from the web handlers.
func runWebWorker(df *dependency.Finder, reqChan <-chan *dependency2.Request) {
for req := range reqChan {
df.Handle(req)
}
}
func main() {
var transdepConf tools.TransdepConfig
var ip string
var port int
secret := make([]byte, 16)
var secretString string
tmpdir := os.Getenv("TMPDIR")
if tmpdir == "" {
tmpdir = "/tmp"
}
flag.IntVar(&transdepConf.JobCount, "jobs", 5, "Indicates the maximum number of concurrent workers")
flag.IntVar(&transdepConf.LRUSizes.DependencyFinder, "dflrusize", 2000, "Indicates the maximum number of concurrent Dependency Finder workers")
flag.IntVar(&transdepConf.LRUSizes.ZoneCutFinder, "zcflrusize", 10000, "Indicates the maximum number of concurrent Zone Cut Finder workers")
flag.IntVar(&transdepConf.LRUSizes.NameResolverFinder, "nrlrusize", 10000, "Indicates the maximum number of concurrent Name Resolver workers")
flag.StringVar(&transdepConf.CacheRootDir, "cachedir", tmpdir, "Specifies the cache directory")
flag.StringVar(&transdepConf.RootHintsFile, "hints", "", "An updated DNS root hint file. If left unspecified, some hardcoded values will be used.")
flag.StringVar(&ip, "bind", "127.0.0.1", "IP address to which the HTTP server will bind and listen")
flag.IntVar(&port, "port", 5000, "Port on which the HTTP server will bind and listen")
flag.Parse()
// A single dependency finder is shared between all web clients. This allows for cache sharing.
df := dependency.NewFinder(&transdepConf, nil)
reqChan := make(chan *dependency2.Request)
for i := 0; i < transdepConf.JobCount; i++ {
go runWebWorker(df, reqChan)
}
// A secret is generated from random. The point of this secret is to be an authentication token allowing graceful
// shutdown
rand.Read(secret[:])
secretString = hex.EncodeToString(secret)
// The URL to call to perform a graceful shutodown is printed on stdout
fmt.Printf("To stop the server, send a query to http://%s:%d/stop?secret=%s\n", ip, port, secretString)
// handles all requests where we want a list of all SPOF, even domain names that are not protected by DNSSEC
http.HandleFunc("/allnames", func(w http.ResponseWriter, req *http.Request) {
handleAllNamesRequests(reqChan, w, req)
})
// handles all requests where we want a list of all SPOF (domain are considered a SPOF candidates only if they are DNSSEC-protected)
http.HandleFunc("/dnssec", func(w http.ResponseWriter, req *http.Request) {
handleDNSSECRequests(reqChan, w, req)
})
// handles all requests where we want a list of all SPOF when IPv4 addresses are unreachable
http.HandleFunc("/break4", func(w http.ResponseWriter, req *http.Request) {
handleNo4Requests(reqChan, w, req)
})
// handles all requests where we want a list of all SPOF when IPv6 addresses are unreachable
http.HandleFunc("/break6", func(w http.ResponseWriter, req *http.Request) {
handleNo6Requests(reqChan, w, req)
})
// handles requests to stop graceful this webservice
http.HandleFunc("/stop", func(w http.ResponseWriter, req *http.Request) {
stopFinder(df, reqChan, secretString, w, req)
})
// start web server and log fatal error that may arise during execution
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", ip, port), nil))
}