mirror of https://github.com/jlelse/GoBlog
Add support for Tor
This commit is contained in:
parent
917dcdab48
commit
ded4294c45
|
@ -7,7 +7,7 @@ WORKDIR /app
|
||||||
RUN go build --tags "libsqlite3 linux sqlite_fts5"
|
RUN go build --tags "libsqlite3 linux sqlite_fts5"
|
||||||
|
|
||||||
FROM alpine:3.13
|
FROM alpine:3.13
|
||||||
RUN apk add --no-cache sqlite-dev tzdata
|
RUN apk add --no-cache sqlite-dev tzdata tor
|
||||||
COPY templates/ /app/templates/
|
COPY templates/ /app/templates/
|
||||||
COPY --from=build /app/GoBlog /bin/
|
COPY --from=build /app/GoBlog /bin/
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
|
@ -30,6 +30,7 @@ type configServer struct {
|
||||||
PublicAddress string `mapstructure:"publicAddress"`
|
PublicAddress string `mapstructure:"publicAddress"`
|
||||||
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
|
||||||
PublicHTTPS bool `mapstructure:"publicHttps"`
|
PublicHTTPS bool `mapstructure:"publicHttps"`
|
||||||
|
Tor bool `mapstructure:"tor"`
|
||||||
SecurityHeaders bool `mapstructure:"securityHeaders"`
|
SecurityHeaders bool `mapstructure:"securityHeaders"`
|
||||||
CSPDomains []string `mapstructure:"cspDomains"`
|
CSPDomains []string `mapstructure:"cspDomains"`
|
||||||
LetsEncryptMail string `mapstructure:"letsEncryptMail"`
|
LetsEncryptMail string `mapstructure:"letsEncryptMail"`
|
||||||
|
|
|
@ -23,6 +23,8 @@ server:
|
||||||
- media.example.com
|
- media.example.com
|
||||||
# Cookies
|
# Cookies
|
||||||
jwtSecret: changeThisWeakSecret # JWT secret to use for Json Web Token in cookies (login and captcha)
|
jwtSecret: changeThisWeakSecret # JWT secret to use for Json Web Token in cookies (login and captcha)
|
||||||
|
# Tor
|
||||||
|
tor: true # Publish onion service, requires Tor to be installed and available in path
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
cache:
|
cache:
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e
|
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/caddyserver/certmagic v0.12.0
|
github.com/caddyserver/certmagic v0.12.0
|
||||||
|
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88
|
github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
@ -36,7 +37,7 @@ require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.6
|
github.com/mattn/go-sqlite3 v1.14.6
|
||||||
github.com/mholt/acmez v0.1.3 // indirect
|
github.com/mholt/acmez v0.1.3 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.4
|
github.com/microcosm-cc/bluemonday v1.0.4
|
||||||
github.com/miekg/dns v1.1.40 // indirect
|
github.com/miekg/dns v1.1.41 // indirect
|
||||||
github.com/mitchellh/go-server-timing v1.0.1
|
github.com/mitchellh/go-server-timing v1.0.1
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||||
|
@ -60,7 +61,7 @@ require (
|
||||||
golang.org/x/mod v0.4.1 // indirect
|
golang.org/x/mod v0.4.1 // indirect
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e // indirect
|
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d // indirect
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
|
11
go.sum
11
go.sum
|
@ -57,6 +57,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw=
|
||||||
|
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -234,8 +236,8 @@ github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDE
|
||||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE=
|
github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE=
|
||||||
|
@ -449,9 +451,10 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
|
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA=
|
||||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|
29
http.go
29
http.go
|
@ -63,6 +63,14 @@ func startServer() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Start Onion service
|
||||||
|
if appConfig.Server.Tor {
|
||||||
|
go func() {
|
||||||
|
torErr := startOnionService(finalHandler)
|
||||||
|
log.Println("Tor failed:", torErr.Error())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Start HTTP(s) server
|
||||||
localAddress := ":" + strconv.Itoa(appConfig.Server.Port)
|
localAddress := ":" + strconv.Itoa(appConfig.Server.Port)
|
||||||
if appConfig.Server.PublicHTTPS {
|
if appConfig.Server.PublicHTTPS {
|
||||||
certmagic.Default.Storage = &certmagic.FileStorage{Path: "data/https"}
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: "data/https"}
|
||||||
|
@ -74,8 +82,6 @@ func startServer() (err error) {
|
||||||
hosts = append(hosts, appConfig.Server.shortPublicHostname)
|
hosts = append(hosts, appConfig.Server.shortPublicHostname)
|
||||||
}
|
}
|
||||||
err = certmagic.HTTPS(hosts, finalHandler)
|
err = certmagic.HTTPS(hosts, finalHandler)
|
||||||
} else if appConfig.Server.SecurityHeaders {
|
|
||||||
err = http.ListenAndServe(localAddress, finalHandler)
|
|
||||||
} else {
|
} else {
|
||||||
err = http.ListenAndServe(localAddress, finalHandler)
|
err = http.ListenAndServe(localAddress, finalHandler)
|
||||||
}
|
}
|
||||||
|
@ -457,23 +463,32 @@ func buildHandler() (http.Handler, error) {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func securityHeaders(next http.Handler) http.Handler {
|
var cspDomains = ""
|
||||||
extraCSPDomains := ""
|
|
||||||
|
func refreshCSPDomains() {
|
||||||
|
cspDomains = ""
|
||||||
if mp := appConfig.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
|
if mp := appConfig.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
|
||||||
if u, err := url.Parse(mp.MediaURL); err == nil {
|
if u, err := url.Parse(mp.MediaURL); err == nil {
|
||||||
extraCSPDomains += " " + u.Hostname()
|
cspDomains += " " + u.Hostname()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(appConfig.Server.CSPDomains) > 0 {
|
if len(appConfig.Server.CSPDomains) > 0 {
|
||||||
extraCSPDomains += " " + strings.Join(appConfig.Server.CSPDomains, " ")
|
cspDomains += " " + strings.Join(appConfig.Server.CSPDomains, " ")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func securityHeaders(next http.Handler) http.Handler {
|
||||||
|
refreshCSPDomains()
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
|
||||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||||
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
||||||
w.Header().Set("Content-Security-Policy", "default-src 'self'"+extraCSPDomains)
|
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
||||||
|
if appConfig.Server.Tor && torAddress != "" {
|
||||||
|
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", torAddress, r.URL.Path))
|
||||||
|
}
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/tor"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
torAddress string
|
||||||
|
)
|
||||||
|
|
||||||
|
func startOnionService(h http.Handler) error {
|
||||||
|
torDataPath, err := filepath.Abs("data/tor")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(torDataPath, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Initialize private key
|
||||||
|
torKeyPath := filepath.Join(torDataPath, "onion.pk")
|
||||||
|
var torKey crypto.PrivateKey
|
||||||
|
if _, err := os.Stat(torKeyPath); os.IsNotExist(err) {
|
||||||
|
_, torKey, err = ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x509Encoded, err := x509.MarshalPKCS8PrivateKey(torKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded})
|
||||||
|
os.WriteFile(torKeyPath, pemEncoded, os.ModePerm)
|
||||||
|
} else {
|
||||||
|
d, _ := os.ReadFile(torKeyPath)
|
||||||
|
block, _ := pem.Decode(d)
|
||||||
|
x509Encoded := block.Bytes
|
||||||
|
torKey, err = x509.ParsePKCS8PrivateKey(x509Encoded)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
|
||||||
|
log.Println("Starting and registering onion service, please wait a couple of minutes...")
|
||||||
|
t, err := tor.Start(nil, &tor.StartConf{
|
||||||
|
TempDataDirBase: os.TempDir(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer t.Close()
|
||||||
|
// Wait at most a few minutes to publish the service
|
||||||
|
listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||||
|
defer listenCancel()
|
||||||
|
// Create a v3 onion service to listen on any port but show as 80
|
||||||
|
onion, err := t.Listen(listenCtx, &tor.ListenConf{
|
||||||
|
Version3: true,
|
||||||
|
Key: torKey,
|
||||||
|
LocalPort: 8888,
|
||||||
|
RemotePorts: []int{80},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer onion.Close()
|
||||||
|
torAddress = onion.String()
|
||||||
|
log.Println("Onion service published on http://" + torAddress)
|
||||||
|
// Serve handler
|
||||||
|
return http.Serve(onion, h)
|
||||||
|
}
|
Loading…
Reference in New Issue