Merge pull request #213 from akosourov/feature/add-ssl-support

This commit is contained in:
Umputun
2018-11-04 11:14:44 -06:00
committed by GitHub
7 changed files with 273 additions and 16 deletions
+4
View File
@@ -119,6 +119,10 @@ _this is the recommended way to run remark42_
| notify.telegram.token | NOTIFY_TELEGRAM_TOKEN | | telegram token |
| notify.telegram.chan | NOTIFY_TELEGRAM_CHAN | | telegram channel |
| notify.telegram.timeout | NOTIFY_TELEGRAM_TIMEOUT | | telegram timeout |
| ssl.type | SSL_TYPE | none | https support, `none` - http, `static` - https |
| ssl.port | SSL_PORT | 8443 | port for https server |
| ssl.cert | SSL_CERT | | path to cert.pem file |
| ssl.key | SSL_KEY | | path to key.pem file |
| max-comment | MAX_COMMENT_SIZE | 2048 | comment's size limit |
| low-score | LOW_SCORE | `-5` | low score threshold |
| critical-score | CRITICAL_SCORE | `-10` | critical score threshold |
+38
View File
@@ -37,6 +37,7 @@ type ServerCommand struct {
Mongo MongoGroup `group:"mongo" namespace:"mongo" env-namespace:"MONGO"`
Admin AdminGroup `group:"admin" namespace:"admin" env-namespace:"ADMIN"`
Notify NotifyGroup `group:"notify" namespace:"notify" env-namespace:"NOTIFY"`
SSL SSLGroup `group:"ssl" namespace:"ssl" env-namespace:"SSL"`
Sites []string `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","`
DevPasswd string `long:"dev-passwd" env:"DEV_PASSWD" default:"" description:"development mode password"`
@@ -129,6 +130,14 @@ type NotifyGroup struct {
} `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"`
}
// SSLGroup defines options group for server ssl params
type SSLGroup struct {
Type string `long:"type" env:"TYPE" description:"ssl (auto)support" choice:"none" choice:"static" choice:"auto" default:"none"`
Port int `long:"port" env:"PORT" description:"port number for https server" default:"8443"`
Cert string `long:"cert" env:"CERT" description:"path to cert.pem file"`
Key string `long:"key" env:"KEY" description:"path to key.pem file"`
}
// serverApp holds all active objects
type serverApp struct {
*ServerCommand
@@ -236,6 +245,11 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
imgProxy := &proxy.Image{Enabled: s.ImageProxy, RoutePath: "/api/v1/img", RemarkURL: s.RemarkURL}
commentFormatter := store.NewCommentFormatter(imgProxy)
sslConfig, err := s.makeSSLConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to make config of ssl server params")
}
srv := &api.Rest{
Version: s.Revision,
DataService: dataService,
@@ -256,6 +270,7 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
},
Cache: loadingCache,
NotifyService: notifyService,
SSLConfig: sslConfig,
}
srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = s.LowScore, s.CriticalScore
@@ -478,3 +493,26 @@ func (s *ServerCommand) makeNotify(dataStore *service.DataStore) (*notify.Servic
}
return nil, errors.Errorf("unsupported notification type %q", s.Notify.Type)
}
func (s *ServerCommand) makeSSLConfig() (group api.SSLConfig, err error) {
switch s.SSL.Type {
case "none":
group.SSLMode = api.None
case "static":
if s.SSL.Cert == "" {
return group, errors.New("path to cert.pem is required")
}
if s.SSL.Key == "" {
return group, errors.New("path to key.pem is required")
}
group.SSLMode = api.Static
group.Port = s.SSL.Port
group.Cert = s.SSL.Cert
group.Key = s.SSL.Key
case "auto":
group.SSLMode = api.Auto
group.Port = s.SSL.Port
return group, errors.New("not implemented yet")
}
return group, err
}
+54
View File
@@ -1,6 +1,7 @@
package main
import (
"crypto/tls"
"io/ioutil"
"net/http"
"os"
@@ -46,3 +47,56 @@ func TestMain(t *testing.T) {
wg.Wait()
}
func TestMain_SSLStaticMode(t *testing.T) {
os.Args = []string{"test", "server", "--secret=123456", "--store.bolt.path=/tmp/xyz", "--backup=/tmp",
"--avatar.fs.path=/tmp", "--port=18080", "--url=https://localhost:18443", "--dbg",
"--ssl.type=static", "--ssl.cert=testdata/cert.pem", "--ssl.key=testdata/key.pem", "--ssl.port=18443"}
go func() {
time.Sleep(500 * time.Millisecond)
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
require.Nil(t, err)
}()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
st := time.Now()
main()
assert.True(t, time.Since(st).Seconds() < 1, "should take about 500msec")
wg.Done()
}()
time.Sleep(200 * time.Millisecond) // let server start
client := http.Client{
// prevent http redirect
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
// allow self-signed certificate
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// check http to https redirect response
resp, err := client.Get("http://localhost:18080/blah?param=1")
require.Nil(t, err)
defer resp.Body.Close()
assert.Equal(t, 307, resp.StatusCode)
assert.Equal(t, "https://localhost:18443/blah?param=1", resp.Header.Get("Location"))
// check https server
resp, err = client.Get("https://localhost:18443/ping")
require.Nil(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.Equal(t, "pong", string(body))
wg.Wait()
}
+51 -16
View File
@@ -53,8 +53,10 @@ type Rest struct {
Critical int
}
httpServer *http.Server
lock sync.Mutex
SSLConfig SSLConfig
httpsServer *http.Server
httpServer *http.Server
lock sync.Mutex
adminService admin
}
@@ -70,22 +72,37 @@ type commentsWithInfo struct {
// Run the lister and request's router, activate rest server
func (s *Rest) Run(port int) {
log.Printf("[INFO] activate rest server on port %d", port)
switch s.SSLConfig.SSLMode {
case None:
log.Printf("[INFO] activate http rest server on port %d", port)
router := s.routes()
s.lock.Lock()
s.httpServer = s.makeHTTPServer(port, s.routes())
s.lock.Unlock()
s.lock.Lock()
s.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 30 * time.Second,
err := s.httpServer.ListenAndServe()
log.Printf("[WARN] http server terminated, %s", err)
case Static:
log.Printf("[INFO] activate rest https server in 'static' mode on port %d", s.SSLConfig.Port)
s.lock.Lock()
s.httpsServer = s.makeHTTPServer(s.SSLConfig.Port, s.routes())
s.httpServer = s.makeHTTPServer(port, s.httpToHTTPSRouter())
s.lock.Unlock()
go func() {
log.Printf("[INFO] activate http redirect server on port %d", port)
err := s.httpServer.ListenAndServe()
log.Printf("[WARN] http redirect server terminated, %s", err)
}()
err := s.httpsServer.ListenAndServeTLS(s.SSLConfig.Cert, s.SSLConfig.Key)
log.Printf("[WARN] https server terminated, %s", err)
case Auto:
log.Printf("[WARN] Autocert mode is not implemented yet")
}
s.lock.Unlock()
err := s.httpServer.ListenAndServe()
log.Printf("[WARN] http server terminated, %s", err)
}
// Shutdown rest http server
@@ -96,13 +113,31 @@ func (s *Rest) Shutdown() {
s.lock.Lock()
if s.httpServer != nil {
if err := s.httpServer.Shutdown(ctx); err != nil {
log.Printf("[DEBUG] rest shutdown error, %s", err)
log.Printf("[DEBUG] http shutdown error, %s", err)
}
log.Print("[DEBUG] shutdown http server completed")
}
if s.httpsServer != nil {
log.Print("[WARN] shutdown https server")
if err := s.httpsServer.Shutdown(ctx); err != nil {
log.Printf("[DEBUG] https shutdown error, %s", err)
}
log.Print("[DEBUG] shutdown https server completed")
}
log.Print("[DEBUG] shutdown rest server completed")
s.lock.Unlock()
}
func (s *Rest) makeHTTPServer(port int, router chi.Router) *http.Server {
return &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 30 * time.Second,
}
}
func (s *Rest) routes() chi.Router {
router := chi.NewRouter()
router.Use(middleware.RealIP, Recoverer)
+47
View File
@@ -0,0 +1,47 @@
package api
import (
"net/http"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
// sslMode defines ssl mode for rest server
type sslMode int8
const (
// None defines to run http server only
None sslMode = iota
// Static defines to run both https and http server. Redirect http to https
Static
// Auto defines to run both https and http server. Redirect http to https. Https server with autocert support
Auto
)
// SSLConfig holds all ssl params for rest server
type SSLConfig struct {
SSLMode sslMode
Cert string
Key string
Port int
}
// httpToHTTPSRouter creates new router which does redirect from http to https server
func (s *Rest) httpToHTTPSRouter() chi.Router {
router := chi.NewRouter()
router.Use(middleware.RealIP, Recoverer)
router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
newURL := s.RemarkURL + r.URL.Path
if r.URL.RawQuery != "" {
newURL += "?" + r.URL.RawQuery
}
http.Redirect(w, r, newURL, http.StatusTemporaryRedirect)
})
return router
}
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEpDCCAowCCQCDOGCCov0x5zANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMTgxMDIwMTIxNjI0WhcNMjgxMDE3MTIxNjI0WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDi
PU80EAvPZizxJhwG1X2XBZv8iVgsTIAJhCYSzLScgn46Yln9s1/LJFtQloDrZByk
m+/3VfaYsrwUT8Xn9NHYnde9q/L9sQM9rfLsVjHkLp3gPaWYRburMfOqrdxZ2GYR
uUi9Ni4s7o4WE1CBCoepiFdVY3OYKQdifygCqZlsAgywkqz7q0qJH+Fkw4OTzf+o
fqr6keF8B6HbHf4Kgwv0/h4EKqZ2YeHqn7bCVS/ppr6t5n7+s/EjN6gD5WnBsxNU
ILjnREW4wG6rN0gY9IaZ6vDeJBqe9fhYPevUH9OZInOlYpHYHt1dHo3sxto64KAp
eZv9/LUuO0QSBOzh6L20HLSqUMQVgsVxOjXpza0DTosQfpEKTkuJxMeedweWClRN
gZY6p7xdFtKju43dfxLsUtSLq3TGFVHWVwtPuSdIDwFRx5JPkL5WgK8XEsN9k7Hs
oA+/Ng98hvNj1kSQi8FXTFnlZ7+YNi51UbhveGCKRZdh6Klm1HwYxQcY6kOWRyv+
QNyeIKlIxhfgsL2iJzZMkM/OL/IXwCROCha/v9tMk7gKmlS7O2MHNILcGP1LsyFJ
EI67ep7LK4q8EZfVPThjS/cTag5zETWoaJ7cxY36WxaeQB+3OgKT0NRiAT88+eKq
Yd5nRJ6LSdSM2rQwqiW8JF4T/Xq10Y0bGoKoqf/ywQIDAQABMA0GCSqGSIb3DQEB
CwUAA4ICAQANcjV3Rzb7sefN7XR77tKpIr0TT2Qk/s4V6hV91vDq+G+7YGi+BRaX
6VWd1OjQVq4YgSmOgQ7L11xEMIZXsNlerwIV0DqLYFYD1Nj6iYdvis4JWDSbFUlI
gJNyvEhC5dtGcHg8mWYGWcQE5Eu2paVrL49madExqvlpIk0cfJVhdviI9t15mJ+X
JBbKaxdoh7CV5oR5cbTJqkszksozMH6krqGZAOgOAn5KzqIZFNyeDUYzPhzjySlh
NNO/sdMyRFfNqZh/bC84z7HB9/KxHnFTBUvUdoX0JRAnkSV5zTuqGFxP/MRdNC1Y
1hre9nnMSzHCoyizFihKBtaTc5OKbV62rl20WSSrDuNzFBGK3jHA3qnL7pdVacXg
+TOt9g0YoKLxQTPzvRGLo3kZbELP2dWzK8ILZeSi2vdJJRGe4Vt3oYF3Ky79ZFW4
wIm8QTpOKHQlH2mpw+OCQ42AKJdBT4f/stJDxuKk7fBHAEzEnH94pLGpi23BJNGR
wSOk+h6Oi0CHEO+Iww5nWX+y4msK7mZwl3eBdYmuXQJnODyGj3n2rjB0Rj51BNXV
APPpGaCbBEDdnTD7iNtwj7By/kVFmzfjAX45C1aF/3B2RS8AAEyEIP1uolykSQQJ
Cdt0GVjDnKk4nG8//FsPXBU9s7UFYa2E68Oz2JUyOyHVKt0xkc0Seg==
-----END CERTIFICATE-----
+52
View File
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDiPU80EAvPZizx
JhwG1X2XBZv8iVgsTIAJhCYSzLScgn46Yln9s1/LJFtQloDrZBykm+/3VfaYsrwU
T8Xn9NHYnde9q/L9sQM9rfLsVjHkLp3gPaWYRburMfOqrdxZ2GYRuUi9Ni4s7o4W
E1CBCoepiFdVY3OYKQdifygCqZlsAgywkqz7q0qJH+Fkw4OTzf+ofqr6keF8B6Hb
Hf4Kgwv0/h4EKqZ2YeHqn7bCVS/ppr6t5n7+s/EjN6gD5WnBsxNUILjnREW4wG6r
N0gY9IaZ6vDeJBqe9fhYPevUH9OZInOlYpHYHt1dHo3sxto64KApeZv9/LUuO0QS
BOzh6L20HLSqUMQVgsVxOjXpza0DTosQfpEKTkuJxMeedweWClRNgZY6p7xdFtKj
u43dfxLsUtSLq3TGFVHWVwtPuSdIDwFRx5JPkL5WgK8XEsN9k7HsoA+/Ng98hvNj
1kSQi8FXTFnlZ7+YNi51UbhveGCKRZdh6Klm1HwYxQcY6kOWRyv+QNyeIKlIxhfg
sL2iJzZMkM/OL/IXwCROCha/v9tMk7gKmlS7O2MHNILcGP1LsyFJEI67ep7LK4q8
EZfVPThjS/cTag5zETWoaJ7cxY36WxaeQB+3OgKT0NRiAT88+eKqYd5nRJ6LSdSM
2rQwqiW8JF4T/Xq10Y0bGoKoqf/ywQIDAQABAoICAGZpwIfd16rMIv4K9Vb2n8KU
rHcWKU7sVm14X8/U0NGklMWTLg+VeoC39Yo2hyeHixvM50T8qgOXrWI4dms5PSEk
2rumsfm7fq8WJkhsAGu92oPfBC45KuolU5to1r3pItNVH6Nfemmml7VQivnPLXhG
GtqxiWtQ72HcE92YrrXg+6OW0i2e7b/DKcH2a4KmFtxMctO44+zEe7V4Dz7s8aW2
vxm9wNsIFj+iixXPgD/x1pFcLRLZnAgIMsuIt2YSzWYg5fLyyekHW1GwVbfOQXw9
iUnV6CMbT4+KgE3nFl9U5CrywviliCmWIkMUBUfv1TDRNxczpUTHWVoa1qyxMeYT
ubfGoBjKzf6/bvTEm/EkBDNZWOJ+fvQZAK8f8Pq6K7KeW61kfW/dM8YzKcKKelXc
yeE+6WG7BH4rLmVRh1UyFfT59oipsTKYwKekDSLCDDhXkpIMV6DCvnPmdO1o/7ed
13VNA2cCV+3Sj0BI3hob74jxo/UflkGX9SGRe7LpYfRkIyjII4ZjCPBiDNSOMKm+
ygRjiPmwjZfmDPgd5EicmAhSb7TdzUmdMssz7z2OWHX97beEt2lV0fIft/xb4u90
eGsafasnY4qfVyJolJ4FYQkkQlvZWoqaBB7cN8VhLG+eU1tqYz6baUOwY0NvWY3E
iekbQ5u7RWVIQg7cuHtBAoIBAQD32jXlQjO9oNBLcibYUQlZWKkQIVcWpTCtMBbC
ZqqV0+Xlk11SPYtWm2x1pUXp22wZk+AFCzskioWfGLBy4Br9GEF3iXLna4uXnb/6
JdzP4i1kpUk3/F1GJCT8GTkps2bdGhYwhkaWK9IIlAN7zoq5t1GA94kYTrYepvCp
SR2pbgpwvqfqe+LTW5xNtOVq+48p9oXuac8YTXBDci7hAzUjLkkC1UAWsummAC/o
1BM7NUB2X3PorIofYG+4qgLHlmDiV0On470J8QZIv79ifMM+ER/a0UjHrmoFErbr
Ze+bAjXSgkfQIihUwXdL6xEB0vinsmaNmGpmkBhSjrwforbJAoIBAQDprTdzyKVM
T7e6I2V1AyaCokBhQfVfn+fTpewgNyw7iYBq6udXoNUPFI7TG23F01/R+0iShEmW
u1hZeKdnX6r5SkvLOC/wZxPCAptfTHs+cKjOiwWXIdNA2dG6rKz50n8cuEDf1uLl
/jN3JLmlyuYZStMVxVOsUoPPTBlASSgbFvaz8mTgol64hUUp335UTp80XteZHWAw
+IZMCp769E3u+87XkaSIZWRw0Grgbsr8XI3rNLq0ZPOtRuvnE1LWAkgSa2Syouru
2a8WIaFUIUlAdfPYFij3qZm8lI5s1WEj8QZnGvDViCgeTASsb4mm/GKZNbqp+kZj
YFgvuUDq+0A5AoIBAQCT9E0MCFL+tgrhnskVhHqCPWze1ig4um+uUvCjJ8pZGl84
hpnEyDxvfORn7jsn+PY05pNqL2Qk0hQl/wUF7kXuJBaglwpin057OY4qc4O3DRtv
40MsCphbkKQLYbs/63pSLopkeSOV7Z65xDG4LHmfl5Et3ZaXThVlgt5TD/SG+ct9
tjJixYWICj3qHTx+eqDhueoFgmA/76o+eOEHLXUBBVZMPKS/aBoWIDJ5WHAKfiV3
BFAa6zSgul6267YxEtHPC6y1gjuMvba0otJKppOeb3aGlzxNBw7+7EcJipZYAB/F
BER+OIW+4qamSTKj1h7dbJ/PktahVw9N6Srf77ARAoIBAQDnoxp1/u7Er707mfzn
ukDBfB7/ige3S+bdqftgj/Cb8BjJY6QB/F9XfJR79hOh539zwnlVLCdacMoKnwyq
722ngpuRjlgPIc63Iihsln8RDVJ4s9zNzO0BuyootyUc3Cm/CXLdsMDpJkxUDaau
D2x6o+6NBTeFeaJJqnDZhetotyHEjUKsXqgwl4NRF8e9wNYWbvGfSTxiLunJlTm1
INnkTd7jV+9WedPP9PoNEibLdOAFyRZO3keje8s7G6+gj1BsWeLPJapnLIhgEuQ+
ZmSbrpESohzoLEpGYqiwf/C0VSd4q38S3M6QAeEcIdC9JDCFGXq5JmwB9K5WWHhe
tsMZAoIBAD0ovavzEBKfrQ0yXTT0W1PDXXPlpIcpD5iGMTmiZez/0GRaXi/kNDgk
nk6KITgbGQ/O5xy0fUK2pDe9ccWLS8pTamjouoDgdfLl0P2rA3/uILecjbq3kGGx
XGxTFc9wgrLSNDLk0v8CdyVRFy6/JFLm19Ya91EjxU7AxIL+z/uuZSU5+VaF/qAW
73o7+SjPtj1h/qk6sejC1hZ/43+LzeOVXs88oFolFR3eyayyJkCsElXuOG0BO+ll
50VA7OQrFZ1XpfD1vIVlhpogXU2QFe0l1NH00i6/l4xXzpWI79oMu5a6S/oWuIGg
Qvy0mUIIWxegXy6aVnRb+iikOHgtnZU=
-----END PRIVATE KEY-----