Set default ciphers, set tls 1.2 via config, set curve prefs (#9315)
Config Checks at StartUp Part1 Config Checks; Tests for TLS Server HSTS header implementation + tests make gofmt happy with new go version... make gofmt happy with new go version #2... fix logic bug fix typo Fix unnecessary code block
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
cedf6488e4
Коммит
557fd9ea18
@@ -46,7 +46,7 @@ type Server struct {
|
|||||||
didFinishListen chan struct{}
|
didFinishListen chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var corsAllowedMethods []string = []string{
|
var corsAllowedMethods = []string{
|
||||||
"POST",
|
"POST",
|
||||||
"GET",
|
"GET",
|
||||||
"OPTIONS",
|
"OPTIONS",
|
||||||
@@ -199,26 +199,75 @@ func (a *App) StartServer() error {
|
|||||||
go func() {
|
go func() {
|
||||||
var err error
|
var err error
|
||||||
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
if *a.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
||||||
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
GetCertificate: m.GetCertificate,
|
PreferServerCipherSuites: true,
|
||||||
|
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch *a.Config().ServiceSettings.TLSMinVer {
|
||||||
|
case "1.0":
|
||||||
|
tlsConfig.MinVersion = tls.VersionTLS10
|
||||||
|
case "1.1":
|
||||||
|
tlsConfig.MinVersion = tls.VersionTLS11
|
||||||
|
default:
|
||||||
|
tlsConfig.MinVersion = tls.VersionTLS12
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCiphers := []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.Config().ServiceSettings.TLSOverwriteCiphers) == 0 {
|
||||||
|
tlsConfig.CipherSuites = defaultCiphers
|
||||||
|
} else {
|
||||||
|
var cipherSuites []uint16
|
||||||
|
for _, cipher := range a.Config().ServiceSettings.TLSOverwriteCiphers {
|
||||||
|
value, ok := model.ServerTLSSupportedCiphers[cipher]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
mlog.Warn("Unsupported cipher passed", mlog.String("cipher", cipher))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherSuites = append(cipherSuites, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cipherSuites) == 0 {
|
||||||
|
mlog.Warn("No supported ciphers passed, fallback to default cipher suite")
|
||||||
|
cipherSuites = defaultCiphers
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.CipherSuites = cipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile := ""
|
||||||
|
keyFile := ""
|
||||||
|
|
||||||
|
if *a.Config().ServiceSettings.UseLetsEncrypt {
|
||||||
|
tlsConfig.GetCertificate = m.GetCertificate
|
||||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
||||||
|
} else {
|
||||||
|
certFile = *a.Config().ServiceSettings.TLSCertFile
|
||||||
|
keyFile = *a.Config().ServiceSettings.TLSKeyFile
|
||||||
|
}
|
||||||
|
|
||||||
a.Srv.Server.TLSConfig = tlsConfig
|
a.Srv.Server.TLSConfig = tlsConfig
|
||||||
err = a.Srv.Server.ServeTLS(listener, "", "")
|
err = a.Srv.Server.ServeTLS(listener, certFile, keyFile)
|
||||||
} else {
|
|
||||||
err = a.Srv.Server.ServeTLS(listener, *a.Config().ServiceSettings.TLSCertFile, *a.Config().ServiceSettings.TLSKeyFile)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err = a.Srv.Server.Serve(listener)
|
err = a.Srv.Server.Serve(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != http.ErrServerClosed {
|
if err != nil && err != http.ErrServerClosed {
|
||||||
mlog.Critical(fmt.Sprintf("Error starting server, err:%v", err))
|
mlog.Critical(fmt.Sprintf("Error starting server, err:%v", err))
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(a.Srv.didFinishListen)
|
close(a.Srv.didFinishListen)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"github.com/mattermost/mattermost-server/utils"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
@@ -16,6 +22,10 @@ func TestStartServerSuccess(t *testing.T) {
|
|||||||
|
|
||||||
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||||
serverErr := a.StartServer()
|
serverErr := a.StartServer()
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
checkEndpoint(t, client, "http://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
a.Shutdown()
|
a.Shutdown()
|
||||||
require.NoError(t, serverErr)
|
require.NoError(t, serverErr)
|
||||||
}
|
}
|
||||||
@@ -48,3 +58,140 @@ func TestStartServerPortUnavailable(t *testing.T) {
|
|||||||
a.Shutdown()
|
a.Shutdown()
|
||||||
require.Error(t, serverErr)
|
require.Error(t, serverErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStartServerTLSSuccess(t *testing.T) {
|
||||||
|
a, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testDir, _ := utils.FindDir("tests")
|
||||||
|
a.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.ServiceSettings.ListenAddress = ":0"
|
||||||
|
*cfg.ServiceSettings.ConnectionSecurity = "TLS"
|
||||||
|
*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
|
||||||
|
*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
|
||||||
|
})
|
||||||
|
serverErr := a.StartServer()
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
|
a.Shutdown()
|
||||||
|
require.NoError(t, serverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartServerTLSVersion(t *testing.T) {
|
||||||
|
a, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testDir, _ := utils.FindDir("tests")
|
||||||
|
a.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.ServiceSettings.ListenAddress = ":0"
|
||||||
|
*cfg.ServiceSettings.ConnectionSecurity = "TLS"
|
||||||
|
*cfg.ServiceSettings.TLSMinVer = "1.2"
|
||||||
|
*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
|
||||||
|
*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
|
||||||
|
})
|
||||||
|
serverErr := a.StartServer()
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
MaxVersion: tls.VersionTLS11,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "remote error: tls: protocol version not supported") {
|
||||||
|
t.Errorf("Expected protocol version error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected nil, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Shutdown()
|
||||||
|
require.NoError(t, serverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartServerTLSOverwriteCipher(t *testing.T) {
|
||||||
|
a, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testDir, _ := utils.FindDir("tests")
|
||||||
|
a.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.ServiceSettings.ListenAddress = ":0"
|
||||||
|
*cfg.ServiceSettings.ConnectionSecurity = "TLS"
|
||||||
|
cfg.ServiceSettings.TLSOverwriteCiphers = []string{
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
}
|
||||||
|
*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")
|
||||||
|
*cfg.ServiceSettings.TLSCertFile = path.Join(testDir, "tls_test_cert.pem")
|
||||||
|
})
|
||||||
|
serverErr := a.StartServer()
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "remote error: tls: handshake failure") {
|
||||||
|
t.Errorf("Expected protocol version error, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected nil, got %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Shutdown()
|
||||||
|
require.NoError(t, serverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkEndpoint(t *testing.T, client *http.Client, url string, expectedStatus int) error {
|
||||||
|
res, err := client.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != expectedStatus {
|
||||||
|
t.Errorf("Response code was %d; want %d", res.StatusCode, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
"ConnectionSecurity": "",
|
"ConnectionSecurity": "",
|
||||||
"TLSCertFile": "",
|
"TLSCertFile": "",
|
||||||
"TLSKeyFile": "",
|
"TLSKeyFile": "",
|
||||||
|
"TLSMinVer": "1.2",
|
||||||
|
"TLSStrictTransport": false,
|
||||||
|
"TLSStrictTransportMaxAge": 63072000,
|
||||||
|
"TLSOverwriteCiphers": [],
|
||||||
"UseLetsEncrypt": false,
|
"UseLetsEncrypt": false,
|
||||||
"LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
|
"LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache",
|
||||||
"Forward80To443": false,
|
"Forward80To443": false,
|
||||||
|
|||||||
12
i18n/en.json
12
i18n/en.json
@@ -4314,6 +4314,18 @@
|
|||||||
"id": "model.config.is_valid.webserver_security.app_error",
|
"id": "model.config.is_valid.webserver_security.app_error",
|
||||||
"translation": "Invalid value for webserver connection security."
|
"translation": "Invalid value for webserver connection security."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.tls_cert_file.app_error",
|
||||||
|
"translation": "Invalid value for TLS certificate file - Either use LetsEncrypt or set path to existing certificate file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.tls_key_file.app_error",
|
||||||
|
"translation": "Invalid value for TLS key file - Either use LetsEncrypt or set path to existing key file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.tls_overwrite_cipher.app_error",
|
||||||
|
"translation": "Invalid value passed for TLS overwrite cipher - Please refer to the documentation for valid values"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.config.is_valid.websocket_url.app_error",
|
"id": "model.config.is_valid.websocket_url.app_error",
|
||||||
"translation": "Websocket URL must be a valid URL and start with ws:// or wss://"
|
"translation": "Websocket URL must be a valid URL and start with ws:// or wss://"
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -174,6 +176,31 @@ const (
|
|||||||
CLIENT_SIDE_CERT_CHECK_SECONDARY_AUTH = "secondary"
|
CLIENT_SIDE_CERT_CHECK_SECONDARY_AUTH = "secondary"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ServerTLSSupportedCiphers = map[string]uint16{
|
||||||
|
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
}
|
||||||
|
|
||||||
type ServiceSettings struct {
|
type ServiceSettings struct {
|
||||||
SiteURL *string
|
SiteURL *string
|
||||||
WebsocketURL *string
|
WebsocketURL *string
|
||||||
@@ -182,6 +209,10 @@ type ServiceSettings struct {
|
|||||||
ConnectionSecurity *string
|
ConnectionSecurity *string
|
||||||
TLSCertFile *string
|
TLSCertFile *string
|
||||||
TLSKeyFile *string
|
TLSKeyFile *string
|
||||||
|
TLSMinVer *string
|
||||||
|
TLSStrictTransport *bool
|
||||||
|
TLSStrictTransportMaxAge *int64
|
||||||
|
TLSOverwriteCiphers []string
|
||||||
UseLetsEncrypt *bool
|
UseLetsEncrypt *bool
|
||||||
LetsEncryptCertificateCacheFile *string
|
LetsEncryptCertificateCacheFile *string
|
||||||
Forward80To443 *bool
|
Forward80To443 *bool
|
||||||
@@ -324,6 +355,22 @@ func (s *ServiceSettings) SetDefaults() {
|
|||||||
s.TLSCertFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE)
|
s.TLSCertFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.TLSMinVer == nil {
|
||||||
|
s.TLSMinVer = NewString("1.2")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TLSStrictTransport == nil {
|
||||||
|
s.TLSStrictTransport = NewBool(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TLSStrictTransportMaxAge == nil {
|
||||||
|
s.TLSStrictTransportMaxAge = NewInt64(63072000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TLSOverwriteCiphers == nil {
|
||||||
|
s.TLSOverwriteCiphers = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
if s.UseLetsEncrypt == nil {
|
if s.UseLetsEncrypt == nil {
|
||||||
s.UseLetsEncrypt = NewBool(false)
|
s.UseLetsEncrypt = NewBool(false)
|
||||||
}
|
}
|
||||||
@@ -2352,6 +2399,32 @@ func (ss *ServiceSettings) isValid() *AppError {
|
|||||||
return NewAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "", http.StatusBadRequest)
|
return NewAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *ss.ConnectionSecurity == CONN_SECURITY_TLS && *ss.UseLetsEncrypt == false {
|
||||||
|
appErr := NewAppError("Config.IsValid", "model.config.is_valid.tls_cert_file.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
|
||||||
|
if *ss.TLSCertFile == "" {
|
||||||
|
return appErr
|
||||||
|
} else if _, err := os.Stat(*ss.TLSCertFile); os.IsNotExist(err) {
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
appErr = NewAppError("Config.IsValid", "model.config.is_valid.tls_key_file.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
|
||||||
|
if *ss.TLSKeyFile == "" {
|
||||||
|
return appErr
|
||||||
|
} else if _, err := os.Stat(*ss.TLSKeyFile); os.IsNotExist(err) {
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss.TLSOverwriteCiphers) > 0 {
|
||||||
|
for _, cipher := range ss.TLSOverwriteCiphers {
|
||||||
|
if _, ok := ServerTLSSupportedCiphers[cipher]; !ok {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.tls_overwrite_cipher.app_error", map[string]interface{}{"name": cipher}, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if *ss.ReadTimeout <= 0 {
|
if *ss.ReadTimeout <= 0 {
|
||||||
return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest)
|
return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|||||||
20
tests/tls_test_cert.pem
Обычный файл
20
tests/tls_test_cert.pem
Обычный файл
@@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDWjCCAkKgAwIBAgIJAK0E8sSdFSXnMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
|
||||||
|
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
|
||||||
|
Q29tcGFueSBMdGQwHhcNMTgxMDA2MjAwMzQ1WhcNMTkxMDA2MjAwMzQ1WjBCMQsw
|
||||||
|
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
|
||||||
|
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||||
|
0VVolTOXKn3sVwKivDJH015wa4f3c+9OrCVwdg7ZTaiebDutLT2kHi/H8cElpi67
|
||||||
|
cE8ios4hPYfF+jJpNF1VMuLUtq93UWwUYhb709AvArMSA5L5Vz+0z/VPysu03jrC
|
||||||
|
o21A4U2G+LXjgOp8oSeRnfHRz6LrQxl1GTl2Kavou41SIpaf7YTM9za51Ya5wnUI
|
||||||
|
6UoCEnsgI2qVgeHdF9dMpV5XY5VM0lROdoZqtpRjJxe5wFZkW6JOpWpirX1h6jPq
|
||||||
|
KsY3CacCwonFrGM11mhcliBEGnUGEqNwBUrTkt2opTJ/L3KLFNC65ukg1y4RfFHz
|
||||||
|
1uQ/IrpNjpClT4AnlwkAmQIDAQABo1MwUTAdBgNVHQ4EFgQUxI+gQOaOrxtW+wHG
|
||||||
|
vFCqeipS1s8wHwYDVR0jBBgwFoAUxI+gQOaOrxtW+wHGvFCqeipS1s8wDwYDVR0T
|
||||||
|
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAKGXu7u23BMKw0sPjLB9Nu3vp
|
||||||
|
0Ova9mflJ4mdI5Y1mClwdN6ysIibC3RDQZbcsz4oEFOh11+z+xLmADhBzEGksuDs
|
||||||
|
9ZV2EAOvlFZpsl/2z1ACDq9NF6ru1slspOp2JRr74LIDo2iU4a1ubW6l1NN7bM3d
|
||||||
|
2AWjKmeaqwyT80sGVO0ZCYO9QsIgX+WR4SyaY1xUATJEs8gW64jBzcJQSnZZbHjt
|
||||||
|
eArVVzwUCAOp1XJhoXP/l9zOg/vtw8PUwb8iPZGK/8UtYTuHgVQUsYr6NAlds4Ag
|
||||||
|
IQMsphOLsvz8tXEsQAvd3e0Sz94/oZP1aDUopom9gPDNGOBykuWW0epBfv950A==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
tests/tls_test_key.pem
Обычный файл
28
tests/tls_test_key.pem
Обычный файл
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRVWiVM5cqfexX
|
||||||
|
AqK8MkfTXnBrh/dz706sJXB2DtlNqJ5sO60tPaQeL8fxwSWmLrtwTyKiziE9h8X6
|
||||||
|
Mmk0XVUy4tS2r3dRbBRiFvvT0C8CsxIDkvlXP7TP9U/Ky7TeOsKjbUDhTYb4teOA
|
||||||
|
6nyhJ5Gd8dHPoutDGXUZOXYpq+i7jVIilp/thMz3NrnVhrnCdQjpSgISeyAjapWB
|
||||||
|
4d0X10ylXldjlUzSVE52hmq2lGMnF7nAVmRbok6lamKtfWHqM+oqxjcJpwLCicWs
|
||||||
|
YzXWaFyWIEQadQYSo3AFStOS3ailMn8vcosU0Lrm6SDXLhF8UfPW5D8iuk2OkKVP
|
||||||
|
gCeXCQCZAgMBAAECggEBALRkQYexuabodO5WWx6KxdKkI4TG2ruRkd5PNSbHjQOb
|
||||||
|
N0pV8tp1sCRDUK5In8UhqG0UBOj/cS2w/y6omniBpZYAWwZDFzOXS8lrvP+++4P8
|
||||||
|
BJ4H3c8OGybKY0SDXw3S3UAwOiTtxk41kCPb7iKCEr5lUUT5RHvCSGLAXc9zUU+s
|
||||||
|
3d4vscDTc/aLzm4e/hlnzA6pPQuz/GustgBDqonQRKTnKZ1pnzl7cBw0rOIJtmHe
|
||||||
|
pViS8g2jtGMB5NoIFj4u08MwKuKJDmr+vQxjMkyx05x+aN4QIR/6b4kw46wridAJ
|
||||||
|
zKIrMwWRXcFQWLs08LuTKmqwdmuDfdzHRJtnTCMvf7ECgYEA7n21yG6EOmeZWRJH
|
||||||
|
5fSwwttXbxx0JrfWhoBam8/RSumnFx1VUj+0vafO6W+USa5HGWJJ5FFRnpA6/OmD
|
||||||
|
Ywv9eHKUY3HtGXiZ536Pt/dPfBLX/FFzMlscCUAT6UvYOqytYngoKQqQeSeHDjPJ
|
||||||
|
HC8QyAW/gKOLvd2wdBHNZOT7Vp8CgYEA4LOz7hqVgHmIEaVSY+4WNCW/m4PPx/oW
|
||||||
|
as1D+V1eqLzzHDVDmivOGiyP+SIN9+BARCdTi8TPDkzkqOOYAt1kU5Neo3DoH9i3
|
||||||
|
0qteQjy1xLA9UqAwOr16YpoPM/pBb6iwNTrj88Kcfs163hhMoDnarq6y1h9UYKgF
|
||||||
|
vJH5cIeudccCgYBRdptLdYSxNoYJCNeKUwS16pp5F60NNKqQkvNgWaJSBnHO0XQ9
|
||||||
|
fglM5y8kSbrLWD5tC0fWN3i7wuSDU3hPst7H78uEFHw6wRlBG9gXrOB3rzAbve6t
|
||||||
|
erWe60Zh4Ehh8m3fPs/pBPTIjZnyXfoKKIGA8YWyeSrYlgsZ+qLAHf9EXQKBgQDb
|
||||||
|
WnI26VqyrXFIkJQKm3yvcX5IKXfoJ1pE7pcB0sU6giHtko2o7kRnxsLRmQ37wb3b
|
||||||
|
Cm0Dj5/1vNinim51tXxgHggQE4N2u1BP5xzAGpXzKXzjsR8D6L6VjQF0Y0QH5awG
|
||||||
|
erPW3U96dcsRDrWW4IN7bW2Fm9X5+WyINhREZx/HNwKBgH/RMcGhiIcpI4IPGmNi
|
||||||
|
udFhUd60izh5Gimyg8yBSPmX1xI6zjwPXiUvHpPWz65OZk1eltZgoDYvSe/rauJv
|
||||||
|
8VtdisTqZwUQG5mogQxo6uofjzKqQQLiJtmOrL1ZzJU8vJNKdiJTIs22wrSxTenj
|
||||||
|
QtIQkWnBO5bOVY/99s5wlzKw
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -75,6 +75,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
|
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
|
||||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil))
|
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil))
|
||||||
|
|
||||||
|
if *c.App.Config().ServiceSettings.TLSStrictTransport {
|
||||||
|
w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", *c.App.Config().ServiceSettings.TLSStrictTransportMaxAge))
|
||||||
|
}
|
||||||
|
|
||||||
if h.IsStatic {
|
if h.IsStatic {
|
||||||
// Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
|
// Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
|
||||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handlerForTest(c *Context, w http.ResponseWriter, r *http.Request) {
|
func handlerForHTTPErrors(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
|
c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ func TestHandlerServeHTTPErrors(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
handler := web.NewHandler(handlerForTest)
|
handler := web.NewHandler(handlerForHTTPErrors)
|
||||||
|
|
||||||
var flagtests = []struct {
|
var flagtests = []struct {
|
||||||
name string
|
name string
|
||||||
@@ -57,3 +57,50 @@ func TestHandlerServeHTTPErrors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handlerForHTTPSecureTransport(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||||
|
a, err := app.New(app.StoreOverride(testStore), app.DisableConfigWatch)
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
a.UpdateConfig(func(config *model.Config) {
|
||||||
|
*config.ServiceSettings.TLSStrictTransport = true
|
||||||
|
*config.ServiceSettings.TLSStrictTransportMaxAge = 6000
|
||||||
|
})
|
||||||
|
|
||||||
|
web := NewWeb(a, a.Srv.Router)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
handler := web.NewHandler(handlerForHTTPSecureTransport)
|
||||||
|
|
||||||
|
request := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||||
|
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(response, request)
|
||||||
|
header := response.Header().Get("Strict-Transport-Security")
|
||||||
|
|
||||||
|
if header == "" {
|
||||||
|
t.Errorf("Strict-Transport-Security expected but not existent")
|
||||||
|
}
|
||||||
|
|
||||||
|
if header != "max-age=6000" {
|
||||||
|
t.Errorf("Expected max-age=6000, got %s", header)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.UpdateConfig(func(config *model.Config) {
|
||||||
|
*config.ServiceSettings.TLSStrictTransport = false
|
||||||
|
})
|
||||||
|
|
||||||
|
request = httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||||
|
|
||||||
|
response = httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(response, request)
|
||||||
|
header = response.Header().Get("Strict-Transport-Security")
|
||||||
|
|
||||||
|
if header != "" {
|
||||||
|
t.Errorf("Strict-Transport-Security header is not expected, but returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user