Add support for Tor

This commit is contained in:
Jan-Lukas Else 2021-03-19 10:10:47 +01:00
parent 917dcdab48
commit ded4294c45
7 changed files with 117 additions and 14 deletions

View File

@ -7,7 +7,7 @@ WORKDIR /app
RUN go build --tags "libsqlite3 linux sqlite_fts5"
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 --from=build /app/GoBlog /bin/
WORKDIR /app

View File

@ -30,6 +30,7 @@ type configServer struct {
PublicAddress string `mapstructure:"publicAddress"`
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
PublicHTTPS bool `mapstructure:"publicHttps"`
Tor bool `mapstructure:"tor"`
SecurityHeaders bool `mapstructure:"securityHeaders"`
CSPDomains []string `mapstructure:"cspDomains"`
LetsEncryptMail string `mapstructure:"letsEncryptMail"`

View File

@ -23,6 +23,8 @@ server:
- media.example.com
# Cookies
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:

5
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e
github.com/boombuler/barcode v1.0.1 // indirect
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/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88
github.com/dgrijalva/jwt-go v3.2.0+incompatible
@ -36,7 +37,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.6
github.com/mholt/acmez v0.1.3 // indirect
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/mapstructure v1.4.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/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
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/text v0.3.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

11
go.sum
View File

@ -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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/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.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
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/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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-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-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-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA=
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-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

29
http.go
View File

@ -63,6 +63,14 @@ func startServer() (err error) {
if err != nil {
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)
if appConfig.Server.PublicHTTPS {
certmagic.Default.Storage = &certmagic.FileStorage{Path: "data/https"}
@ -74,8 +82,6 @@ func startServer() (err error) {
hosts = append(hosts, appConfig.Server.shortPublicHostname)
}
err = certmagic.HTTPS(hosts, finalHandler)
} else if appConfig.Server.SecurityHeaders {
err = http.ListenAndServe(localAddress, finalHandler)
} else {
err = http.ListenAndServe(localAddress, finalHandler)
}
@ -457,23 +463,32 @@ func buildHandler() (http.Handler, error) {
return r, nil
}
func securityHeaders(next http.Handler) http.Handler {
extraCSPDomains := ""
var cspDomains = ""
func refreshCSPDomains() {
cspDomains = ""
if mp := appConfig.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
if u, err := url.Parse(mp.MediaURL); err == nil {
extraCSPDomains += " " + u.Hostname()
cspDomains += " " + u.Hostname()
}
}
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) {
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
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)
})
}

81
tor.go Normal file
View File

@ -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)
}