/
server.go
417 lines (362 loc) · 9.82 KB
/
server.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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
package server
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/revoke"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/rkcloudchain/rksync-ca/config"
caerrors "github.com/rkcloudchain/rksync-ca/errors"
"github.com/rkcloudchain/rksync-ca/metadata"
"github.com/rkcloudchain/rksync-ca/util"
)
const (
defaultClientAuth = "noclientcert"
apiPathPrefix = "/api/v1/"
)
// endpoint is an endpoint method on a server
type endpoint func(s *Server, resp http.ResponseWriter, rep *http.Request) (interface{}, error)
var endpoints map[string]endpoint
// Server is the rksync-ca server
type Server struct {
// The home directory for the server
HomeDir string
// BlockingStart if true makes the Start function blocking
BlockingStart bool
// The server's configuration
Config *config.ServerConfig
// The server mux
mux *mux.Router
// The current listener for this server
listener net.Listener
// Server's default CA
CA
// An error which occurs when serving
serverError error
// channel for communication between http.serve and main goroutine
done chan struct{}
}
// Init initializes a rksync-ca server
func (s *Server) Init(renew bool) (err error) {
err = s.init(renew)
err2 := s.CA.closeDB()
if err2 != nil {
log.Errorf("Close DB failed: %s", err2)
}
return err
}
// Initializes the server leaving the DB open
func (s *Server) init(renew bool) (err error) {
serverVersion := metadata.GetVersion()
log.Infof("Server Version: %s", serverVersion)
err = s.initConfig()
if err != nil {
return err
}
err = s.initDefaultCA(renew)
if err != nil {
return err
}
return nil
}
func (s *Server) initConfig() (err error) {
if s.HomeDir == "" {
s.HomeDir, err = os.Getwd()
if err != nil {
return errors.Wrap(err, "Failed to get server's home directory")
}
}
absoluteHomeDir, err := filepath.Abs(s.HomeDir)
if err != nil {
return errors.Errorf("Failed to make server's home directory path absolute: %s", err)
}
s.HomeDir = absoluteHomeDir
if s.Config == nil {
s.Config = new(config.ServerConfig)
}
revoke.SetCRLFetcher(s.fetchCRL)
s.makeFileNamesAbsolute()
return nil
}
func (s *Server) initDefaultCA(renew bool) error {
log.Debugf("Initializing default CA in directory %s", s.HomeDir)
ca := &s.CA
err := initCA(ca, s.HomeDir, s.CA.Config, renew)
if err != nil {
return err
}
log.Infof("Home directory for default CA: %s", ca.HomeDir)
return nil
}
// Read the CRL from body of http response
func (s *Server) fetchCRL(r io.Reader) ([]byte, error) {
crlSizeLimit := s.Config.CRLSizeLimit
log.Debugf("CRL size limit is %d bytes", crlSizeLimit)
crl := make([]byte, crlSizeLimit)
crl, err := util.Read(r, crl)
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("Error reading CRL with max buffer size of %d", crlSizeLimit))
}
return crl, nil
}
// Make all file names in the config absolute
func (s *Server) makeFileNamesAbsolute() error {
log.Debug("Making server filenames abosulte")
return config.AbsTLSServer(&s.Config.TLS, s.HomeDir)
}
// Start the rksync-ca server
func (s *Server) Start() (err error) {
log.Infof("Starting server in home directory: %s", s.HomeDir)
s.serverError = nil
if s.listener != nil {
return errors.New("server is already started")
}
err = s.init(false)
if err != nil {
err2 := s.CA.closeDB()
if err2 != nil {
log.Errorf("Close DB failed: %s", err2)
}
return err
}
s.registerHandlers()
err = s.listenAndServe()
if err != nil {
err2 := s.CA.closeDB()
if err2 != nil {
log.Errorf("Close DB failed: %s", err2)
}
return err
}
return nil
}
// Stop the server
func (s *Server) Stop() error {
err := s.closeListener()
if err != nil {
return err
}
if s.done == nil {
return nil
}
ForEnd:
for {
select {
case <-s.done:
log.Debugf("Stop: successful stop on port %d", s.Config.Port)
close(s.done)
s.done = nil
return nil
case <-time.After(10 * time.Second):
log.Debugf("Stop: waiting for listener on port %d to stop timeout", s.Config.Port)
break ForEnd
}
}
log.Debugf("Stop: successful stop on port %d", s.Config.Port)
err = s.CA.closeDB()
if err != nil {
log.Errorf("Close DB failed: %s", err)
}
return nil
}
// Starting listening and serving
func (s *Server) listenAndServe() (err error) {
var listener net.Listener
var clientAuth tls.ClientAuthType
var ok bool
c := s.Config
if c.Address == "" {
c.Address = config.DefaultServerAddr
}
if c.Port == 0 {
c.Port = config.DefaultServerPort
}
addr := net.JoinHostPort(c.Address, strconv.Itoa(c.Port))
var addrStr string
if c.TLS.Enabled {
log.Debug("TLS is enabled")
addrStr = fmt.Sprintf("https://%s", addr)
if !util.FileExists(c.TLS.KeyFile) {
return errors.Errorf("File specified by 'tls.keyfile' does not exists: %s", c.TLS.KeyFile)
} else if !util.FileExists(c.TLS.CertFile) {
return errors.Errorf("File specified by 'tls.certfile' does not exists: %s", c.TLS.CertFile)
}
log.Debugf("TLS Certificate: %s, TLS Key: %s", c.TLS.CertFile, c.TLS.KeyFile)
cer, err := util.LoadX509KeyPair(c.TLS.CertFile, c.TLS.KeyFile, s.csp)
if err != nil {
return err
}
if c.TLS.ClientAuth.Type == "" {
c.TLS.ClientAuth.Type = defaultClientAuth
}
log.Debugf("Client authentication type requested: %s", c.TLS.ClientAuth.Type)
authType := strings.ToLower(c.TLS.ClientAuth.Type)
if clientAuth, ok = clientAuthTypes[authType]; !ok {
return errors.New("Invalid client auth type provided")
}
var certPool *x509.CertPool
if authType != defaultClientAuth {
certPool, err = LoadPEMCertPool(c.TLS.ClientAuth.CertFiles)
if err != nil {
return err
}
}
config := &tls.Config{
Certificates: []tls.Certificate{*cer},
ClientAuth: clientAuth,
ClientCAs: certPool,
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS12,
CipherSuites: config.DefaultCipherSuites,
}
listener, err = tls.Listen("tcp", addr, config)
if err != nil {
return errors.Wrapf(err, "TLS listen failed for %s", addrStr)
}
} else {
addrStr = fmt.Sprintf("http://%s", addr)
listener, err = net.Listen("tcp", addr)
if err != nil {
return errors.Wrapf(err, "TCP listen failed for %s", addrStr)
}
}
s.listener = listener
log.Infof("Listening on %s", addrStr)
if s.BlockingStart {
return s.serve()
}
s.done = make(chan struct{})
go s.serve()
return nil
}
func (s *Server) serve() error {
listener := s.listener
if listener == nil {
return nil
}
s.serverError = http.Serve(listener, s.mux)
log.Errorf("Server has stopped serving: %s", s.serverError)
s.closeListener()
err := s.CA.closeDB()
if err != nil {
log.Errorf("Close DB failed: %s", err)
}
if s.done != nil {
s.done <- struct{}{}
}
return s.serverError
}
// Closes the listening endpoint
func (s *Server) closeListener() error {
s.mutex.Lock()
defer s.mutex.Unlock()
port := s.Config.Port
if s.listener == nil {
msg := fmt.Sprintf("Stop: listener was already closed on port %d", port)
log.Debug(msg)
return errors.New(msg)
}
err := s.listener.Close()
s.listener = nil
if err != nil {
log.Debugf("Stop: failed to close listener on port %d: %s", port, err)
return err
}
log.Debugf("Stop: successfully closed listener on port %d", port)
return nil
}
func (s *Server) registerHandlers() {
s.mux = mux.NewRouter()
s.registerHandler("enroll", enrollHandler, http.MethodPost)
s.registerHandler("register", registerHandler, http.MethodPost)
}
func (s *Server) registerHandler(path string, e endpoint, methods ...string) {
bound := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return e(s, resp, req)
}
s.mux.Handle("/"+path, s.wrap(bound)).Methods(methods...)
s.mux.Handle(apiPathPrefix+path, s.wrap(bound)).Methods(methods...)
}
func (s *Server) wrap(handler func(http.ResponseWriter, *http.Request) (interface{}, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Debugf("Received request for %s", r.URL.String())
resp, err := handler(w, r)
he := s.getHTTPErr(err)
w.Header().Set("Connection", "Keep-Alive")
if r.Method == http.MethodHead {
w.Header().Set("Content-Length", "0")
} else {
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Content-Type", "application/json")
}
if he != nil {
w.WriteHeader(he.GetStatusCode())
log.Infof(`%s %s %s %d %d "%s"`, r.RemoteAddr, r.Method, r.URL, he.GetStatusCode(), he.GetLocalCode(), he.GetLocalMsg())
} else {
w.WriteHeader(http.StatusOK)
log.Infof(`%s %s %s %d 0 "OK"`, r.RemoteAddr, r.Method, r.URL, http.StatusOK)
}
if r.Method != http.MethodHead {
w.Write([]byte(`{"result":`))
if resp != nil {
s.writeJSON(resp, w)
} else {
w.Write([]byte(`""`))
}
w.Write([]byte(`,"errors":[`))
if he != nil {
rm := &api.ResponseMessage{Code: he.GetRemoteCode(), Message: he.GetRemoteMsg()}
s.writeJSON(rm, w)
}
w.Write([]byte(`],"messages":[],"success":`))
if he != nil {
w.Write([]byte(`false}`))
} else {
w.Write([]byte(`true}`))
}
}
}
}
func (s *Server) writeJSON(obj interface{}, w http.ResponseWriter) {
enc := json.NewEncoder(w)
err := enc.Encode(obj)
if err != nil {
log.Errorf("Failed encoding response to JSON: %s", err)
}
}
func (s *Server) getHTTPErr(err error) *caerrors.HTTPErr {
if err == nil {
return nil
}
type causer interface {
Cause() error
}
curErr := err
for curErr != nil {
switch curErr.(type) {
case *caerrors.HTTPErr:
return curErr.(*caerrors.HTTPErr)
case causer:
curErr = curErr.(causer).Cause()
default:
return caerrors.CreateHTTPErr(500, caerrors.ErrUnknown, err.Error())
}
}
return caerrors.CreateHTTPErr(500, caerrors.ErrUnknown, "nil error")
}
// GetCA returns the CA instance
func (s *Server) GetCA() *CA {
return &s.CA
}