mirror of
https://github.com/jech/galene.git
synced 2024-11-09 18:25:58 +01:00
Update TLS certificates, generate certificate if not found.
We now notice that a TLS certificate on disk has changed, and we generate a self-signed certificate if none is found.
This commit is contained in:
parent
6cf82f6e0c
commit
b1babf5b77
3 changed files with 121 additions and 12 deletions
16
README
16
README
|
@ -4,15 +4,11 @@
|
||||||
|
|
||||||
CGO_ENABLED=0 go build -ldflags='-s -w'
|
CGO_ENABLED=0 go build -ldflags='-s -w'
|
||||||
|
|
||||||
## Create a server certificate
|
|
||||||
|
|
||||||
mkdir data
|
|
||||||
openssl req -newkey rsa:2048 -nodes -keyout data/key.pem -x509 -days 365 -out data/cert.pem
|
|
||||||
|
|
||||||
## Set the server administrator credentials
|
## Set the server administrator credentials
|
||||||
|
|
||||||
This step is optional.
|
This step is optional.
|
||||||
|
|
||||||
|
mkdir data
|
||||||
echo 'god:topsecret' > data/passwd
|
echo 'god:topsecret' > data/passwd
|
||||||
|
|
||||||
## Set up a group
|
## Set up a group
|
||||||
|
@ -110,6 +106,16 @@ For a 32-bit MIPS board with no hardware floating point (WNDR3800, etc.):
|
||||||
Set up a user *galene* on your server, then do:
|
Set up a user *galene* on your server, then do:
|
||||||
|
|
||||||
rsync -a galene static data groups galene@server.example.org:
|
rsync -a galene static data groups galene@server.example.org:
|
||||||
|
|
||||||
|
If you don't have a TLS certificate, Galène will generate a self-signed
|
||||||
|
certificate automatically (and print a warning to the logs). If you have
|
||||||
|
a certificate, install it in the files `data/cert.pem` and `data/key.pem`:
|
||||||
|
|
||||||
|
ssh galene@server.example.org
|
||||||
|
sudo cp /etc/letsencrypt/live/server.example.org/fullchain.pem data/cert.pem
|
||||||
|
sudo cp /etc/letsencrypt/live/server.example.org/key.pem data/key.pem
|
||||||
|
sudo chown galene:galene data/*.pem
|
||||||
|
sudo chmod go-rw data/key.pem
|
||||||
|
|
||||||
Now run the binary on the server:
|
Now run the binary on the server:
|
||||||
|
|
||||||
|
|
98
webserver/certificate.go
Normal file
98
webserver/certificate.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package webserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type certInfo struct {
|
||||||
|
certificate *tls.Certificate
|
||||||
|
keyTime time.Time
|
||||||
|
certTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var certificate atomic.Value
|
||||||
|
|
||||||
|
func generateCertificate(dataDir string) (tls.Certificate, error) {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
NotBefore: now,
|
||||||
|
NotAfter: now.Add(365 * 24 * time.Hour),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
bytes, err := x509.CreateCertificate(
|
||||||
|
rand.Reader, &template, &template, &priv.PublicKey, priv,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tls.Certificate{
|
||||||
|
Certificate: [][]byte{bytes},
|
||||||
|
PrivateKey: priv,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileTime(filename string) time.Time {
|
||||||
|
fi, err := os.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
log.Printf("%v: %v", filename, err)
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return fi.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertificate(dataDir string) (*tls.Certificate, error) {
|
||||||
|
info, ok := certificate.Load().(*certInfo)
|
||||||
|
|
||||||
|
certFile := filepath.Join(dataDir, "cert.pem")
|
||||||
|
keyFile := filepath.Join(dataDir, "key.pem")
|
||||||
|
certTime := fileTime(certFile)
|
||||||
|
keyTime := fileTime(keyFile)
|
||||||
|
|
||||||
|
if !ok || !info.certTime.Equal(certTime) || !info.keyTime.Equal(keyTime) {
|
||||||
|
var cert tls.Certificate
|
||||||
|
if certTime.Equal(time.Time{}) || keyTime.Equal(time.Time{}) {
|
||||||
|
log.Printf("Generating self-signed certificate")
|
||||||
|
var err error
|
||||||
|
cert, err = generateCertificate(dataDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info = &certInfo{
|
||||||
|
certificate: &cert,
|
||||||
|
certTime: certTime,
|
||||||
|
keyTime: keyTime,
|
||||||
|
}
|
||||||
|
certificate.Store(info)
|
||||||
|
}
|
||||||
|
return info.certificate, nil
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package webserver
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -55,6 +56,13 @@ func Serve(address string, dataDir string) error {
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
ReadHeaderTimeout: 60 * time.Second,
|
||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: 120 * time.Second,
|
||||||
}
|
}
|
||||||
|
if !Insecure {
|
||||||
|
s.TLSConfig = &tls.Config{
|
||||||
|
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
return getCertificate(dataDir)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
s.RegisterOnShutdown(func() {
|
s.RegisterOnShutdown(func() {
|
||||||
group.Range(func(g *group.Group) bool {
|
group.Range(func(g *group.Group) bool {
|
||||||
go g.Shutdown("server is shutting down")
|
go g.Shutdown("server is shutting down")
|
||||||
|
@ -67,10 +75,7 @@ func Serve(address string, dataDir string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !Insecure {
|
if !Insecure {
|
||||||
err = s.ListenAndServeTLS(
|
err = s.ListenAndServeTLS("", "")
|
||||||
filepath.Join(dataDir, "cert.pem"),
|
|
||||||
filepath.Join(dataDir, "key.pem"),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
err = s.ListenAndServe()
|
err = s.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
@ -273,8 +278,8 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path != "/group/" + name {
|
if r.URL.Path != "/group/"+name {
|
||||||
http.Redirect(w, r, "/group/" + name,
|
http.Redirect(w, r, "/group/"+name,
|
||||||
http.StatusPermanentRedirect)
|
http.StatusPermanentRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -433,7 +438,7 @@ func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) {
|
||||||
fmt.Fprintf(w, "</body></html>\n")
|
fmt.Fprintf(w, "</body></html>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
var wsUpgrader = websocket.Upgrader {
|
var wsUpgrader = websocket.Upgrader{
|
||||||
HandshakeTimeout: 30 * time.Second,
|
HandshakeTimeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue