mirror of https://github.com/jlelse/GoBlog
Support custom ACME server, HTTP-01 challenge and external accout binding for automatic HTTPS
This commit is contained in:
parent
b04bf3be46
commit
634f139bae
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *goBlog) getAutocertManager() *autocert.Manager {
|
||||||
|
if a.tailscaleEnabled() || !a.cfg.Server.PublicHTTPS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if a.autocertManager != nil {
|
||||||
|
return a.autocertManager
|
||||||
|
}
|
||||||
|
// Not initialized yet
|
||||||
|
a.autocertInit.Do(func() {
|
||||||
|
// Create hosts whitelist
|
||||||
|
hosts := []string{a.cfg.Server.publicHostname}
|
||||||
|
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
||||||
|
hosts = append(hosts, shn)
|
||||||
|
}
|
||||||
|
if mhn := a.cfg.Server.mediaHostname; mhn != "" {
|
||||||
|
hosts = append(hosts, mhn)
|
||||||
|
}
|
||||||
|
// Create autocert manager
|
||||||
|
acmeDir := acme.LetsEncryptURL
|
||||||
|
if a.cfg.Server.AcmeDir != "" {
|
||||||
|
acmeDir = a.cfg.Server.AcmeDir
|
||||||
|
}
|
||||||
|
m := &autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(hosts...),
|
||||||
|
Cache: &httpsCache{db: a.db},
|
||||||
|
Client: &acme.Client{DirectoryURL: acmeDir, HTTPClient: a.httpClient},
|
||||||
|
}
|
||||||
|
// Set external account binding
|
||||||
|
if a.cfg.Server.AcmeEabKid != "" && a.cfg.Server.AcmeEabKey != "" {
|
||||||
|
key, err := base64.RawURLEncoding.DecodeString(a.cfg.Server.AcmeEabKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.ExternalAccountBinding = &acme.ExternalAccountBinding{
|
||||||
|
KID: a.cfg.Server.AcmeEabKid,
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save
|
||||||
|
a.autocertManager = m
|
||||||
|
})
|
||||||
|
// Return
|
||||||
|
return a.autocertManager
|
||||||
|
}
|
|
@ -209,5 +209,5 @@ func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *htt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set(contentType, contenttype.ASUTF8)
|
w.Header().Set(contentType, contenttype.ASUTF8)
|
||||||
a.min.Get().Minify(contenttype.AS, w, buf)
|
_ = a.min.Get().Minify(contenttype.AS, w, buf)
|
||||||
}
|
}
|
||||||
|
|
4
app.go
4
app.go
|
@ -13,6 +13,7 @@ import (
|
||||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"go.goblog.app/app/pkgs/minify"
|
"go.goblog.app/app/pkgs/minify"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
)
|
)
|
||||||
|
@ -29,6 +30,9 @@ type goBlog struct {
|
||||||
// Assets
|
// Assets
|
||||||
assetFileNames map[string]string
|
assetFileNames map[string]string
|
||||||
assetFiles map[string]*assetFile
|
assetFiles map[string]*assetFile
|
||||||
|
// Autocert
|
||||||
|
autocertManager *autocert.Manager
|
||||||
|
autocertInit sync.Once
|
||||||
// Blogroll
|
// Blogroll
|
||||||
blogrollCacheGroup singleflight.Group
|
blogrollCacheGroup singleflight.Group
|
||||||
// Blogstats
|
// Blogstats
|
||||||
|
|
|
@ -42,6 +42,9 @@ type configServer struct {
|
||||||
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
||||||
MediaAddress string `mapstructure:"mediaAddress"`
|
MediaAddress string `mapstructure:"mediaAddress"`
|
||||||
PublicHTTPS bool `mapstructure:"publicHttps"`
|
PublicHTTPS bool `mapstructure:"publicHttps"`
|
||||||
|
AcmeDir string `mapstructure:"acmeDir"`
|
||||||
|
AcmeEabKid string `mapstructure:"acmeEabKid"`
|
||||||
|
AcmeEabKey string `mapstructure:"acmeEabKey"`
|
||||||
TailscaleHTTPS bool `mapstructure:"tailscaleHttps"`
|
TailscaleHTTPS bool `mapstructure:"tailscaleHttps"`
|
||||||
Tailscale *configTailscale `mapstructure:"tailscale"`
|
Tailscale *configTailscale `mapstructure:"tailscale"`
|
||||||
Tor bool `mapstructure:"tor"`
|
Tor bool `mapstructure:"tor"`
|
||||||
|
|
|
@ -28,6 +28,10 @@ server:
|
||||||
mediaAddress: https://media.example.com # Optional domain to use for serving media files
|
mediaAddress: https://media.example.com # Optional domain to use for serving media files
|
||||||
# Security
|
# Security
|
||||||
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
|
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
|
||||||
|
# To use another ACME server like ZeroSSL, set the following
|
||||||
|
# acmeDir: https://acme.zerossl.com/v2/DV90
|
||||||
|
# acmeEabKid: "kid" # Key ID for the EAB key
|
||||||
|
# acmeEabKey: "key" # Key for the EAB key
|
||||||
securityHeaders: true # Set security HTTP headers (to always use HTTPS etc.)
|
securityHeaders: true # Set security HTTP headers (to always use HTTPS etc.)
|
||||||
cspDomains: # Specify additional domains to allow embedded content with enabled securityHeaders
|
cspDomains: # Specify additional domains to allow embedded content with enabled securityHeaders
|
||||||
- media.example.com
|
- media.example.com
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -43,7 +43,7 @@ require (
|
||||||
github.com/paulmach/go.geojson v1.4.0
|
github.com/paulmach/go.geojson v1.4.0
|
||||||
github.com/posener/wstest v1.2.0
|
github.com/posener/wstest v1.2.0
|
||||||
github.com/pquerna/otp v1.3.0
|
github.com/pquerna/otp v1.3.0
|
||||||
github.com/samber/lo v1.11.0
|
github.com/samber/lo v1.12.0
|
||||||
github.com/schollz/sqlite3dump v1.3.1
|
github.com/schollz/sqlite3dump v1.3.1
|
||||||
github.com/snabb/sitemap v1.0.0
|
github.com/snabb/sitemap v1.0.0
|
||||||
github.com/spf13/cast v1.4.1
|
github.com/spf13/cast v1.4.1
|
||||||
|
@ -57,8 +57,8 @@ require (
|
||||||
github.com/yuin/goldmark v1.4.11
|
github.com/yuin/goldmark v1.4.11
|
||||||
// master
|
// master
|
||||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||||
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -391,8 +391,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w=
|
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samber/lo v1.11.0 h1:JfeYozXL1xfkhRUFOfH13ociyeiLSC/GRJjGKI668xM=
|
github.com/samber/lo v1.12.0 h1:UZXQYR2N6FST+QWxnjV7gPmT9KkXL9eWbZxkrefVs88=
|
||||||
github.com/samber/lo v1.11.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
github.com/samber/lo v1.12.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||||
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
|
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
|
||||||
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
|
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
|
||||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
|
@ -488,8 +488,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||||
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -575,8 +575,8 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
|
||||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
6
http.go
6
http.go
|
@ -62,9 +62,13 @@ func (a *goBlog) startServer() (err error) {
|
||||||
if a.cfg.Server.PublicHTTPS || a.cfg.Server.TailscaleHTTPS {
|
if a.cfg.Server.PublicHTTPS || a.cfg.Server.TailscaleHTTPS {
|
||||||
go func() {
|
go func() {
|
||||||
// Start HTTP server for redirects
|
// Start HTTP server for redirects
|
||||||
|
h := http.Handler(http.HandlerFunc(a.redirectToHttps))
|
||||||
|
if m := a.getAutocertManager(); m != nil {
|
||||||
|
h = m.HTTPHandler(h)
|
||||||
|
}
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: ":80",
|
Addr: ":80",
|
||||||
Handler: http.HandlerFunc(a.redirectToHttps),
|
Handler: h,
|
||||||
ReadTimeout: 5 * time.Minute,
|
ReadTimeout: 5 * time.Minute,
|
||||||
WriteTimeout: 5 * time.Minute,
|
WriteTimeout: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/crypto/acme"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,24 +14,11 @@ func (a *goBlog) getTCPListener(s *http.Server) (net.Listener, error) {
|
||||||
// Tailscale listener
|
// Tailscale listener
|
||||||
return a.getTailscaleListener(s.Addr)
|
return a.getTailscaleListener(s.Addr)
|
||||||
} else if s.Addr == ":443" && a.cfg.Server.PublicHTTPS {
|
} else if s.Addr == ":443" && a.cfg.Server.PublicHTTPS {
|
||||||
// Listener with public HTTPS
|
m := a.getAutocertManager()
|
||||||
hosts := []string{a.cfg.Server.publicHostname}
|
if m == nil {
|
||||||
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
|
return nil, errors.New("autocert not initialized")
|
||||||
hosts = append(hosts, shn)
|
|
||||||
}
|
}
|
||||||
if mhn := a.cfg.Server.mediaHostname; mhn != "" {
|
return a.getAutocertManager().Listener(), nil
|
||||||
hosts = append(hosts, mhn)
|
|
||||||
}
|
|
||||||
acmeDir := acme.LetsEncryptURL
|
|
||||||
// Uncomment for Staging Let's Encrypt
|
|
||||||
// acmeDir = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
||||||
m := &autocert.Manager{
|
|
||||||
Prompt: autocert.AcceptTOS,
|
|
||||||
HostPolicy: autocert.HostWhitelist(hosts...),
|
|
||||||
Cache: &httpsCache{db: a.db},
|
|
||||||
Client: &acme.Client{DirectoryURL: acmeDir},
|
|
||||||
}
|
|
||||||
return m.Listener(), nil
|
|
||||||
} else if s.Addr == ":443" && a.cfg.Server.TailscaleHTTPS {
|
} else if s.Addr == ":443" && a.cfg.Server.TailscaleHTTPS {
|
||||||
// Listener with Tailscale TLS config
|
// Listener with Tailscale TLS config
|
||||||
ln, err := net.Listen("tcp", s.Addr)
|
ln, err := net.Listen("tcp", s.Addr)
|
||||||
|
|
Loading…
Reference in New Issue