-
Notifications
You must be signed in to change notification settings - Fork 0
/
serve.go
145 lines (127 loc) · 4.39 KB
/
serve.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
// Copyright (c) 2022. Alvin Baena.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/alvinbaena/pwd-checker/internal/api"
"github.com/gin-contrib/logger"
"github.com/gin-gonic/gin"
"github.com/likexian/selfca"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
var (
serveCmd = &cobra.Command{
Use: "serve",
Short: "Serve the API for querying the Pwned Password GCS database",
RunE: func(cmd *cobra.Command, args []string) error {
return serveCommand()
},
}
)
//goland:noinspection GoUnhandledErrorResult
func init() {
serveCmd.Flags().StringVarP(&inputFile, "in-file", "i", "", "Pwned Passwords GCS input file (required)")
serveCmd.MarkFlagRequired("in-file")
serveCmd.Flags().BoolVar(&selfTLS, "self-tls", false,
"If the server should use a self-signed certificate when starting. The certificate is renewed on each server restart")
serveCmd.Flags().StringVar(&tlsCert, "tls-cert", "", "Path to the PEM encoded TLS certificate to be used by the server")
serveCmd.Flags().StringVar(&tlsCert, "tls-key", "", "Path to the PEM encoded TLS private key to be used by the server")
serveCmd.Flags().Uint16VarP(&port, "port", "p", 3100, "Port to be used by the server")
rootCmd.AddCommand(serveCmd)
}
func serveCommand() error {
applyCliSettings(verbose, profile, pprofPort)
if !verbose {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
router.Use(gin.Recovery())
router.Use(logger.SetLogger(logger.WithLogger(func(c *gin.Context, z zerolog.Logger) zerolog.Logger {
return zerolog.New(gin.DefaultWriter).With().Timestamp().Logger()
})))
v1 := router.Group("/v1")
pwned := v1.Group("/check")
if err := api.RegisterQueryApi(pwned, inputFile); err != nil {
return fmt.Errorf("error initializing API: %s", err)
}
srvAddr := fmt.Sprintf(":%d", port)
srv := &http.Server{
Addr: srvAddr,
Handler: router,
}
go func() {
log.Info().Msgf("starting TLS Server on address: %s", srvAddr)
if tlsCert != "" && tlsKey != "" {
// service connections with tls certs
if err := srv.ListenAndServeTLS(tlsCert, tlsKey); err != nil && err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("error starting server")
}
} else if selfTLS {
log.Warn().Msgf("using auto self-signed certificate for TLS. This is not recommended for production. Please consider using your own certificates.")
caConfig := selfca.Certificate{
IsCA: true,
KeySize: 2048,
NotBefore: time.Now(),
// 30 day self-signed cert.
NotAfter: time.Now().Add(time.Duration(30*24) * time.Hour),
}
// generating the certificate
certificate, key, err := selfca.GenerateCertificate(caConfig)
if err != nil {
log.Fatal().Err(err).Msg("error generating auto self-signed certificate")
}
pair, err := tls.X509KeyPair(
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate}),
pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}),
)
if err != nil {
log.Fatal().Err(err).Msg("error using auto self-signed certificate")
}
srv.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{pair},
}
// service connections with tls config, no need to pass files
if err = srv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("error starting server")
}
} else {
log.Fatal().Msg("server requires TLS configuration to start. " +
"Please use either the --self-tls flag or set a certificate with the --tls-cert and --tls-key flags")
}
}()
gracefulShutdown(srv)
return nil
}
func gracefulShutdown(srv *http.Server) {
// Wait for interrupt signal to gracefully shut down the server with
// a timeout.
quit := make(chan os.Signal)
// kill (no param) default send syscall.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall. SIGKILL but can't be a catch, so don't need to add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Info().Msg("shutting down server")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Warn().Err(err).Msg("server Shutdown.")
}
// catching ctx.Done(). timeout of 5 seconds.
select {
case <-ctx.Done():
// Nothing for now
}
log.Info().Msg("server exiting...")
}