Introduce tls support
Introduce TLS support for weblug. TLS can be enabled in the config file. Multiple certificates are supported.
This commit is contained in:
parent
d5f79eae01
commit
1935b32a7f
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -25,3 +25,7 @@ vendor/
|
|||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# Possible key and cert files
|
||||
*.key
|
||||
*.pem
|
||||
*.cert
|
||||
|
|
|
@ -30,3 +30,8 @@ tasks:
|
|||
- test -f weblug
|
||||
cmds:
|
||||
- sudo bash -c "cd test && ./blackbox.sh"
|
||||
certs:
|
||||
cmds:
|
||||
- openssl genrsa -out weblug.key 2048
|
||||
- openssl req -new -x509 -sha256 -key weblug.key -subj "/C=XX/ST=None/L=Nirwana/O=Adeptus Mechanicus/OU=Cult Mechanicus/CN=example1.local" -addext "subjectAltName = DNS:example1.local" -out weblug1.pem -days 365
|
||||
- openssl req -new -x509 -sha256 -key weblug.key -subj "/C=XX/ST=None/L=Nirwana/O=Adeptus Mechanicus/OU=Cult Mechanicus/CN=example1.local" -addext "subjectAltName = DNS:example1.local" -out weblug2.pem -days 365
|
||||
|
|
|
@ -13,12 +13,19 @@ type Config struct {
|
|||
}
|
||||
|
||||
type ConfigSettings struct {
|
||||
BindAddress string `yaml:"bind"` // Bind address for the webserver
|
||||
UID int `yaml:"uid"` // Custom user ID or 0, if not being used
|
||||
GID int `yaml:"gid"` // Custom group ID or 0, if not being used
|
||||
ReadTimeout int `yaml:"readtimeout"` // Timeout for reading the whole request
|
||||
WriteTimeout int `yaml:"writetimeout"` // Timeout for writing the whole response
|
||||
MaxHeaderBytes int `yaml:"maxheadersize"` // Maximum size of the receive body
|
||||
BindAddress string `yaml:"bind"` // Bind address for the webserver
|
||||
UID int `yaml:"uid"` // Custom user ID or 0, if not being used
|
||||
GID int `yaml:"gid"` // Custom group ID or 0, if not being used
|
||||
ReadTimeout int `yaml:"readtimeout"` // Timeout for reading the whole request
|
||||
WriteTimeout int `yaml:"writetimeout"` // Timeout for writing the whole response
|
||||
MaxHeaderBytes int `yaml:"maxheadersize"` // Maximum size of the receive body
|
||||
TLS TLSSettings `yaml:"tls"`
|
||||
}
|
||||
|
||||
type TLSSettings struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Keyfile string `yaml:"keyfile"`
|
||||
Certificates []string `yaml:"certificates"`
|
||||
}
|
||||
|
||||
func (cf *Config) SetDefaults() {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
type Hook struct {
|
||||
Name string `yaml:"name"` // name of the hook
|
||||
Route string `yaml:"route"` // http route
|
||||
Hosts []string `yaml:"hosts"` // allowed remote hosts
|
||||
Command string `yaml:"command"` // Actual command to execute
|
||||
Background bool `yaml:"background"` // Run in background
|
||||
Concurrency int `yaml:"concurrency"` // Number of allowed concurrent runs
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -120,15 +121,62 @@ func main() {
|
|||
|
||||
awaitTerminationSignal()
|
||||
|
||||
log.Printf("Launching webserver on %s", cf.Settings.BindAddress)
|
||||
server := &http.Server{
|
||||
Addr: cf.Settings.BindAddress,
|
||||
ReadTimeout: time.Duration(cf.Settings.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cf.Settings.WriteTimeout) * time.Second,
|
||||
MaxHeaderBytes: cf.Settings.MaxHeaderBytes,
|
||||
if cf.Settings.TLS.Enabled {
|
||||
// Note: Sanity check happens at program startup, here we assume all settings are good
|
||||
log.Printf("Launching tls webserver on %s", cf.Settings.BindAddress)
|
||||
server := &http.Server{
|
||||
Addr: cf.Settings.BindAddress,
|
||||
ReadTimeout: time.Duration(cf.Settings.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cf.Settings.WriteTimeout) * time.Second,
|
||||
MaxHeaderBytes: cf.Settings.MaxHeaderBytes,
|
||||
}
|
||||
tlsConfig := &tls.Config{}
|
||||
// Create self-signed certificate, when no keyfile and no certificates are present
|
||||
if cf.Settings.TLS.Keyfile == "" && len(cf.Settings.TLS.Certificates) == 0 {
|
||||
// TODO
|
||||
fmt.Fprintf(os.Stderr, "error: creating self-signed certificates is not yet implemented")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
var err error
|
||||
// Ensure keyfile AND certificates are present.
|
||||
if cf.Settings.TLS.Keyfile == "" {
|
||||
fmt.Fprintf(os.Stderr, "no keyfile defined\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(cf.Settings.TLS.Certificates) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "no certificates defined\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load certificates
|
||||
tlsConfig.Certificates = make([]tls.Certificate, len(cf.Settings.TLS.Certificates))
|
||||
for i, cert := range cf.Settings.TLS.Certificates {
|
||||
tlsConfig.Certificates[i], err = tls.LoadX509KeyPair(cert, cf.Settings.TLS.Keyfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: certificate '%s' invalid: %s\n", cert, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
listener, err := tls.Listen("tcp", cf.Settings.BindAddress, tlsConfig)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: cannot listen on '%s': %s\n", cf.Settings.BindAddress, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = server.Serve(listener)
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
log.Printf("Launching webserver on %s", cf.Settings.BindAddress)
|
||||
server := &http.Server{
|
||||
Addr: cf.Settings.BindAddress,
|
||||
ReadTimeout: time.Duration(cf.Settings.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cf.Settings.WriteTimeout) * time.Second,
|
||||
MaxHeaderBytes: cf.Settings.MaxHeaderBytes,
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
log.Fatal(err)
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
log.Fatal(err)
|
||||
log.Printf("unexpected end of main loop")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
@ -137,6 +185,23 @@ func createHandler(hook Hook) Handler {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GET %s %s", r.RemoteAddr, hook.Name)
|
||||
|
||||
// Check if this hook is remote-host limited
|
||||
if len(hook.Hosts) > 0 {
|
||||
allowed := false
|
||||
for _, host := range hook.Hosts {
|
||||
if r.RemoteAddr == host {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
// Hook doesn't exist for this host.
|
||||
respondNotFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if adresses are allowed or blocked
|
||||
allowed, err := hook.IsAddressAllowed(r.RemoteAddr)
|
||||
if err != nil {
|
||||
|
@ -228,8 +293,7 @@ func createDefaultHandler() Handler {
|
|||
w.WriteHeader(200)
|
||||
fmt.Fprintf(w, "<!DOCTYPE html><html><head><title>weblug</title></head>\n<body><p><a href=\"https://codeberg.org/grisu48/weblug\">weblug</a> - webhook receiver program</p>\n</body></html>")
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprintf(w, "not found\n")
|
||||
respondNotFound(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,3 +304,9 @@ func createRobotsHandler() Handler {
|
|||
fmt.Fprintf(w, "User-agent: *\nDisallow: /")
|
||||
}
|
||||
}
|
||||
|
||||
func respondNotFound(w http.ResponseWriter, r *http.Request) error {
|
||||
w.WriteHeader(404)
|
||||
_, err := fmt.Fprintf(w, "not found\n")
|
||||
return err
|
||||
}
|
||||
|
|
13
weblug.yml
13
weblug.yml
|
@ -11,12 +11,25 @@ settings:
|
|||
readtimeout: 10 # if set, maximum number of seconds to receive the full request
|
||||
writetimeout: 10 # if set, maximum number of seconds to send the full response
|
||||
maxheadersize: 4096 # maximum header size
|
||||
# Enable TLS here here
|
||||
tls:
|
||||
enabled: true
|
||||
keyfile: 'weblug.key'
|
||||
certificates:
|
||||
- weblug1.pem
|
||||
- weblug2.pem
|
||||
|
||||
# hook definitions. A hook needs to define the HTTP endpoint ("route") and the command
|
||||
# See the following examples for more possible options.
|
||||
hooks:
|
||||
- name: 'hook one'
|
||||
route: "/webhooks/1"
|
||||
|
||||
# if hosts is present, limit requests to this remote hosts
|
||||
# Currently multiplexing the same routes to different hosts does not work.
|
||||
hosts:
|
||||
- example1.local
|
||||
|
||||
command: "sleep 5"
|
||||
background: True # Terminate http request immediately
|
||||
concurrency: 2 # At most 2 parallel processes are allowed
|
||||
|
|
Loading…
Reference in a new issue