mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2026-10-19 16:14:39 +00:00
feature (https): support for HTTPS on the server
This commit is contained in:
@@ -12,7 +12,8 @@ const (
|
||||
CONFIG_PATH = "data/state/config/"
|
||||
DB_PATH = "data/state/db/"
|
||||
FTS_PATH = "data/state/db/search/"
|
||||
TMP_PATH = "data/cache/tmp/"
|
||||
TMP_PATH = "data/cache/tmp/"
|
||||
CERT_PATH = "data/certs"
|
||||
COOKIE_NAME_AUTH = "auth"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_NAME_ADMIN = "admin"
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetCertificate(key *rsa.PrivateKey, root *x509.Certificate) (*x509.Certificate, []byte, error) {
|
||||
if cert, certPEM, err := pullCertificateFromFS(); err == nil {
|
||||
return cert, certPEM, nil
|
||||
}
|
||||
cert, certPEM, err := generateNewCertificate(root, key)
|
||||
if err != nil {
|
||||
Clear()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = saveCertificateToFS(certPEM); err != nil {
|
||||
Clear()
|
||||
return nil, nil, err
|
||||
}
|
||||
return cert, certPEM, nil
|
||||
}
|
||||
|
||||
func generateNewCertificate(root *x509.Certificate, key *rsa.PrivateKey) (*x509.Certificate, []byte, error) {
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, root, root, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certDER);
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cert, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE", Bytes: certDER,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func pullCertificateFromFS() (*x509.Certificate, []byte, error) {
|
||||
file, err := os.OpenFile(certPEMPath, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
certPEM, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block, _ := pem.Decode(certPEM)
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cert, certPEM, nil
|
||||
}
|
||||
|
||||
func saveCertificateToFS(certPEM []byte) error {
|
||||
file, err := os.OpenFile(certPEMPath, os.O_WRONLY | os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.Write(certPEM)
|
||||
file.Close()
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func clearCert() {
|
||||
os.Remove(certPEMPath)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
)
|
||||
|
||||
func GenerateSelfSigned() (tls.Certificate, *x509.CertPool, error) {
|
||||
var err error
|
||||
var key *rsa.PrivateKey
|
||||
var root *x509.Certificate
|
||||
var keyPEM []byte
|
||||
var certPEM []byte
|
||||
var TLSCert tls.Certificate
|
||||
|
||||
if key, keyPEM, err = GetPrivateKey(); err != nil {
|
||||
Log.Error("[https] key_generation %v", err)
|
||||
Clear()
|
||||
return TLSCert, nil, err
|
||||
}
|
||||
if root, err = GetRoot(); err != nil {
|
||||
Log.Error("[https] root_certificate %v", err)
|
||||
Clear()
|
||||
return TLSCert, nil, err
|
||||
}
|
||||
if _, certPEM, err = GetCertificate(key, root); err != nil {
|
||||
Log.Error("[https] x509_certificate %v", err)
|
||||
Clear()
|
||||
return TLSCert, nil, err
|
||||
}
|
||||
if TLSCert, err = tls.X509KeyPair(certPEM, keyPEM); err != nil {
|
||||
Log.Error("[https] tls_certificate %v", err)
|
||||
Clear()
|
||||
return TLSCert, nil, err
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if ok := roots.AppendCertsFromPEM([]byte(certPEM)); ok == false {
|
||||
Log.Error("[https] tls_client")
|
||||
Clear()
|
||||
return TLSCert, nil, err
|
||||
}
|
||||
return TLSCert, roots, nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"path/filepath"
|
||||
"os"
|
||||
)
|
||||
|
||||
var keyPEMPath string = filepath.Join(GetCurrentDir(), CERT_PATH, "key.pem")
|
||||
var certPEMPath string = filepath.Join(GetCurrentDir(), CERT_PATH, "cert.pem")
|
||||
|
||||
func init() {
|
||||
os.MkdirAll(filepath.Join(GetCurrentDir(), CERT_PATH), os.ModePerm)
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
clearPrivateKey()
|
||||
clearCert()
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetPrivateKey() (*rsa.PrivateKey, []byte, error) {
|
||||
if private, privatePEM, err := pullPrivateKeyFromFS(); err == nil {
|
||||
return private, privatePEM, nil
|
||||
}
|
||||
key, keyPEM, err := generateNewPrivateKey()
|
||||
if err != nil {
|
||||
Clear()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = savePrivateKeyToFS(keyPEM); err != nil {
|
||||
Clear()
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, keyPEM, nil
|
||||
}
|
||||
|
||||
func generateNewPrivateKey() (*rsa.PrivateKey, []byte, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) {
|
||||
file, err := os.OpenFile(keyPEMPath, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
keyPEM, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block, _ := pem.Decode(keyPEM)
|
||||
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return priv, keyPEM, nil
|
||||
}
|
||||
|
||||
func savePrivateKeyToFS(privatePEM []byte) error {
|
||||
file, err := os.OpenFile(keyPEMPath, os.O_WRONLY | os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.Write(privatePEM)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearPrivateKey() {
|
||||
os.Remove(keyPEMPath)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetRoot() (*x509.Certificate, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Filestash"},
|
||||
},
|
||||
NotBefore: time.Now().Add(- 24 * time.Hour),
|
||||
NotAfter: time.Now().Add(24 * 365 * 100 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
IPAddresses: func() []net.IP {
|
||||
ips := []net.IP{}
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return []net.IP{ net.ParseIP("127.0.0.1") }
|
||||
}
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
return []net.IP{ net.ParseIP("127.0.0.1") }
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}(),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package plg_start_https
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/filestash/server/common/ssl"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
domain := Config.Get("general.host").String()
|
||||
|
||||
Hooks.Register.Starter(func (r *mux.Router) {
|
||||
Log.Info("[https] starting...%s", domain)
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":https"),
|
||||
Handler: r,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
},
|
||||
PreferServerCipherSuites: true,
|
||||
CurvePreferences: []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.X25519,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch domain {
|
||||
case "":
|
||||
TLSCert, roots, err := ssl.GenerateSelfSigned()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
srv.TLSConfig.Certificates = []tls.Certificate{ TLSCert }
|
||||
HTTPClient.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||
RootCAs: roots,
|
||||
}
|
||||
HTTP.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||
RootCAs: roots,
|
||||
}
|
||||
default:
|
||||
mngr := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(domain),
|
||||
Cache: autocert.DirCache(filepath.Join(GetCurrentDir(), CERT_PATH)),
|
||||
}
|
||||
srv.TLSConfig.GetCertificate = mngr.GetCertificate
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServeTLS("", ""); err != nil {
|
||||
Log.Error("[https]: listen_serve %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
srv := http.Server{
|
||||
Addr: fmt.Sprintf(":http"),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Connection", "close")
|
||||
http.Redirect(
|
||||
w,
|
||||
req,
|
||||
"https://" + req.Host + req.URL.String(),
|
||||
http.StatusMovedPermanently,
|
||||
)
|
||||
}),
|
||||
}
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
Log.Error("[https]: http_redirect %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
go ensureAppHasBooted("https://127.0.0.1/about", fmt.Sprintf("[https] started"))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func ensureAppHasBooted(address string, message string) {
|
||||
i := 0
|
||||
for {
|
||||
if i > 10 {
|
||||
Log.Warning("[https] no boot")
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
res, err := HTTPClient.Get(address)
|
||||
if err != nil {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
Log.Info(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user