164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
// Package service manages the webhook HTTP service.
|
|
package service
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
|
|
"github.com/adnanh/webhook/internal/pidfile"
|
|
"github.com/adnanh/webhook/internal/service/security"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// Service is the webhook HTTP service.
|
|
type Service struct {
|
|
// Address is the listener address for the service (e.g. "127.0.0.1:9000")
|
|
Address string
|
|
|
|
// TLS settings
|
|
enableTLS bool
|
|
tlsCiphers []uint16
|
|
tlsMinVersion uint16
|
|
kpr *security.KeyPairReloader
|
|
|
|
// Future TLS settings to consider:
|
|
// - tlsMaxVersion
|
|
// - configurable TLS curves
|
|
// - modern and intermediate helpers that follows Mozilla guidelines
|
|
// - ca root and intermediate certs
|
|
|
|
listener net.Listener
|
|
server *http.Server
|
|
|
|
pidFile *pidfile.PIDFile
|
|
|
|
// Hooks map[string]hook.Hooks
|
|
}
|
|
|
|
// New creates a new webhook HTTP service for the given address and port.
|
|
func New(ip string, port int) *Service {
|
|
return &Service{
|
|
Address: fmt.Sprintf("%s:%d", ip, port),
|
|
server: &http.Server{},
|
|
tlsMinVersion: tls.VersionTLS12,
|
|
}
|
|
}
|
|
|
|
// Listen announces the TCP service on the local network.
|
|
//
|
|
// To enable TLS, ensure that SetTLSEnabled is called prior to Listen.
|
|
//
|
|
// After calling Listen, Serve must be called to begin serving HTTP requests.
|
|
// The steps are separated so that we can drop privileges, if necessary, after
|
|
// opening the listening port.
|
|
func (s *Service) Listen() error {
|
|
ln, err := net.Listen("tcp", s.Address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !s.enableTLS {
|
|
s.listener = ln
|
|
return nil
|
|
}
|
|
|
|
if s.kpr == nil {
|
|
panic("Listen called with TLS enabled but KPR is nil")
|
|
}
|
|
|
|
c := &tls.Config{
|
|
GetCertificate: s.kpr.GetCertificateFunc(),
|
|
CipherSuites: s.tlsCiphers,
|
|
CurvePreferences: security.GetTLSCurves(""),
|
|
MinVersion: s.tlsMinVersion,
|
|
PreferServerCipherSuites: true,
|
|
}
|
|
|
|
s.listener = tls.NewListener(ln, c)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Serve begins accepting incoming HTTP connections.
|
|
func (s *Service) Serve() error {
|
|
s.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) // disable http/2
|
|
|
|
if s.listener == nil {
|
|
err := s.Listen()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
defer s.listener.Close()
|
|
return s.server.Serve(s.listener)
|
|
}
|
|
|
|
// SetHTTPHandler sets the underly HTTP server Handler.
|
|
func (s *Service) SetHTTPHandler(r *mux.Router) {
|
|
s.server.Handler = r
|
|
}
|
|
|
|
// SetTLSCiphers sets the supported TLS ciphers.
|
|
func (s *Service) SetTLSCiphers(suites string) {
|
|
s.tlsCiphers = security.GetTLSCipherSuites(suites)
|
|
}
|
|
|
|
// SetTLSEnabled enables TLS for the service. Must be called prior to Listen.
|
|
func (s *Service) SetTLSEnabled() {
|
|
s.enableTLS = true
|
|
}
|
|
|
|
// TLSEnabled return true if TLS is enabled for the service.
|
|
func (s *Service) TLSEnabled() bool {
|
|
return s.enableTLS
|
|
}
|
|
|
|
// SetTLSKeyPair sets the TLS key pair for the service.
|
|
func (s *Service) SetTLSKeyPair(certPath, keyPath string) error {
|
|
if certPath == "" {
|
|
return fmt.Errorf("error: certificate required for TLS")
|
|
}
|
|
|
|
if keyPath == "" {
|
|
return fmt.Errorf("error: key required for TLS")
|
|
}
|
|
|
|
var err error
|
|
|
|
s.kpr, err = security.NewKeyPairReloader(certPath, keyPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReloadTLSKeyPair attempts to reload the configured TLS certificate key pair.
|
|
func (s *Service) ReloadTLSKeyPair() error {
|
|
return s.kpr.Reload()
|
|
}
|
|
|
|
// SetTLSMinVersion sets the minimum support TLS version, such as "v1.3".
|
|
func (s *Service) SetTLSMinVersion(ver string) (err error) {
|
|
s.tlsMinVersion, err = security.GetTLSVersion(ver)
|
|
return err
|
|
}
|
|
|
|
// CreatePIDFile creates a new PID file at path p.
|
|
func (s *Service) CreatePIDFile(p string) (err error) {
|
|
s.pidFile, err = pidfile.New(p)
|
|
return err
|
|
}
|
|
|
|
// DeletePIDFile deletes a previously created PID file.
|
|
func (s *Service) DeletePIDFile() error {
|
|
if s.pidFile != nil {
|
|
return s.pidFile.Remove()
|
|
}
|
|
return nil
|
|
}
|