1
0
Fork 0

some bigger changes

- The detection of unix and TCP sockets is much better;
- The configuration is now easier exachangable;
- middleware doesn't get lost;
- the defaults func is now supplying the config it needs into the defaults;
Dieser Commit ist enthalten in:
Sebastian Tobie 2021-11-07 18:51:39 +01:00
Ursprung aa0592bca9
Commit 6d43861ffc
7 geänderte Dateien mit 170 neuen und 61 gelöschten Zeilen

Datei anzeigen

@ -2,6 +2,7 @@ package funcs
import ( import (
"context" "context"
"net"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
"github.com/phuslu/log" "github.com/phuslu/log"
@ -34,3 +35,24 @@ func (PGXLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data m
} }
var _ pgx.Logger = PGXLogger{} var _ pgx.Logger = PGXLogger{}
// IsUnix tests if the address is an unix address. It returns false if its an tcp address.
func IsUnix(address string) bool {
if IsTCP(address) {
return false
}
if _, err := net.ResolveUnixAddr("unix", address); err == nil {
return true
}
return false
}
// IsTCP tests if the address is an tcp address
func IsTCP(address string) bool {
_, err := net.ResolveTCPAddr("tcp", address)
if err == nil {
return true
}
return false
}

47
funcs/funcs_test.go Normale Datei
Datei anzeigen

@ -0,0 +1,47 @@
package funcs_test
import (
"testing"
"go.sebtobie.de/httpserver/funcs"
)
type testtype struct {
name string
arg string
want bool
}
func TestIsUnix(t *testing.T) {
tests := []testtype{
{name: "absolute unix", arg: "/tmp/xyz", want: true},
{name: "relative unix", arg: "xyz", want: true},
{name: "simple tcp4 address", arg: "127.0.0.1:0", want: false},
{name: "simple tcp6 address", arg: "[::1]:0", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := funcs.IsUnix(tt.arg); got != tt.want {
t.Errorf("IsUnix() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsTCP(t *testing.T) {
tests := []testtype{
{name: "simple tcp4 address", arg: "127.0.0.1:0", want: true},
{name: "simple tcp6 address", arg: "[::1]:0", want: true},
{name: "too big ip address", arg: "999.0.0.1:0", want: false},
{name: "too big port", arg: "127.0.0.1:65536", want: false},
{name: "unix relative", arg: "xyz.sock", want: false},
{name: "unix absolute", arg: "/xyz.sock", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := funcs.IsTCP(tt.arg); got != tt.want {
t.Errorf("IsTCP() = %v, want %v", got, tt.want)
}
})
}
}

2
go.mod
Datei anzeigen

@ -28,7 +28,7 @@ require (
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/ugorji/go v1.2.6 // indirect github.com/ugorji/go v1.2.6 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/dgrijalva/jwt-go.v3 v3.2.0 gopkg.in/dgrijalva/jwt-go.v3 v3.2.0

2
go.sum
Datei anzeigen

@ -351,6 +351,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA= golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 h1:Ywe/f3fNleF8I6F6qv3MeFoSZ6CTf2zBMMa/7qVML8M=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

80
http.go
Datei anzeigen

@ -11,26 +11,29 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/phuslu/log" "github.com/phuslu/log"
"go.sebtobie.de/httpserver/auth" "go.sebtobie.de/httpserver/auth"
"go.sebtobie.de/httpserver/funcs"
"go.sebtobie.de/httpserver/menus" "go.sebtobie.de/httpserver/menus"
"go.sebtobie.de/httpserver/templates" "go.sebtobie.de/httpserver/templates"
) )
func init() { func init() {
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Trace().Msgf("%-4s(%02d): %-20s %s", httpMethod, nuHandlers, absolutePath, handlerName) log.Debug().Msgf("%-4s(%02d): %-20s %s", httpMethod, nuHandlers, absolutePath, handlerName)
} }
gin.SetMode(gin.DebugMode)
} }
// Config that is used to map the toml config to the settings that are used. // Config that is used to map the toml config to the settings that are used.
type Config struct { type Config struct {
Addr []string Addr []string
TLSAddr []string TLSAddr []string
TLSconfig *tls.Config TLSconfig *tls.Config `toml:"-"`
Certfile string Certfile string
Keyfile string Keyfile string
Sites map[string]SiteConfig Sites map[string]SiteConfig
} }
/**
// MarshalObject adds the information over the object to the *log.Entry // MarshalObject adds the information over the object to the *log.Entry
func (c *Config) MarshalObject(e *log.Entry) { func (c *Config) MarshalObject(e *log.Entry) {
e.Strs("Address", c.Addr).Bool("TLS", c.TLSconfig != nil) e.Strs("Address", c.Addr).Bool("TLS", c.TLSconfig != nil)
@ -38,9 +41,11 @@ func (c *Config) MarshalObject(e *log.Entry) {
e.Str("Certfile", c.Certfile) e.Str("Certfile", c.Certfile)
e.Str("Keyfile", c.Keyfile) e.Str("Keyfile", c.Keyfile)
} }
e.Int("sites", len(c.Sites))
} }
var _ log.ObjectMarshaler = &Config{} var _ log.ObjectMarshaler = &Config{}
/**/
// Server is an wrapper for the *http.Server and *gin.Engine // Server is an wrapper for the *http.Server and *gin.Engine
type Server struct { type Server struct {
@ -54,6 +59,7 @@ type Server struct {
routines sync.WaitGroup routines sync.WaitGroup
setup bool setup bool
authh auth.AuthenticationHandler authh auth.AuthenticationHandler
middleware gin.HandlersChain
} }
// CreateServer creates an server that can be run in a coroutine. // CreateServer creates an server that can be run in a coroutine.
@ -63,11 +69,16 @@ func CreateServer() *Server {
gin.DefaultWriter = log.DefaultLogger.Std(log.DebugLevel, log.Context{}, "GIN", 0).Writer() gin.DefaultWriter = log.DefaultLogger.Std(log.DebugLevel, log.Context{}, "GIN", 0).Writer()
log.Info().Msg("Creating HTTP-Server") log.Info().Msg("Creating HTTP-Server")
var server = &Server{ var server = &Server{
Conf: &Config{}, Conf: &Config{
TLSconfig: &tls.Config{},
Sites: map[string]SiteConfig{},
},
mrouter: map[string]*gin.Engine{}, mrouter: map[string]*gin.Engine{},
authh: &auth.AnonAccountHandler{}, authh: &auth.AnonAccountHandler{},
menu: []menus.Menu{}, menu: []menus.Menu{},
NotFoundHandler: http.NotFoundHandler(), NotFoundHandler: http.NotFoundHandler(),
sites: map[string]Site{},
middleware: gin.HandlersChain{},
} }
server.http = &http.Server{ server.http = &http.Server{
ErrorLog: log.DefaultLogger.Std(log.ErrorLevel, log.Context{}, "", 0), ErrorLog: log.DefaultLogger.Std(log.ErrorLevel, log.Context{}, "", 0),
@ -78,25 +89,36 @@ func CreateServer() *Server {
// runPort runs a listener on the port. his enables th server to serve more than a address. // runPort runs a listener on the port. his enables th server to serve more than a address.
func (s *Server) runPort(address string, tls bool) { func (s *Server) runPort(address string, tls bool) {
defer s.routines.Done()
var socket net.Listener var socket net.Listener
var err error var err error
if net.ParseIP(address) != nil { var unix string
if funcs.IsTCP(address) {
socket, err = net.Listen("tcp", address) socket, err = net.Listen("tcp", address)
} else { }
if funcs.IsUnix(address) {
unix = "Unix-"
socket, err = net.Listen("unix", address) socket, err = net.Listen("unix", address)
} }
if err != nil { if err != nil {
log.Error().Err(err).Msgf("failed to open socket on %s", address) log.Error().Err(err).Msgf("failed to open socket on %s", address)
return
}
if socket == nil {
log.Error().Msg("Failed to identify the sockettype")
return
} }
if tls { if tls {
log.Info().Msgf("starting listen on secure %ssocket %s", unix, address)
err = s.http.ServeTLS(socket, s.Conf.Certfile, s.Conf.Keyfile) err = s.http.ServeTLS(socket, s.Conf.Certfile, s.Conf.Keyfile)
} else { } else {
log.Info().Msgf("starting listen on %ssocket %s", unix, address)
err = s.http.Serve(socket) err = s.http.Serve(socket)
} }
if err != http.ErrServerClosed { if err != http.ErrServerClosed {
log.Error().Err(err).Msg("Socket unexpected exited") log.Error().Err(err).Msg("Socket unexpected exited")
} }
s.routines.Done()
} }
// SetAuthentication sets the handler that is responsible for authentication // SetAuthentication sets the handler that is responsible for authentication
@ -113,7 +135,6 @@ func (s *Server) StartServer() {
} }
log.Info().Msg("Starting server") log.Info().Msg("Starting server")
s.http.TLSConfig = s.Conf.TLSconfig s.http.TLSConfig = s.Conf.TLSconfig
var err error
if s.Conf.Certfile != "" && s.Conf.Keyfile != "" { if s.Conf.Certfile != "" && s.Conf.Keyfile != "" {
for _, addr := range s.Conf.TLSAddr { for _, addr := range s.Conf.TLSAddr {
s.routines.Add(1) s.routines.Add(1)
@ -121,11 +142,9 @@ func (s *Server) StartServer() {
} }
} }
for _, addr := range s.Conf.Addr { for _, addr := range s.Conf.Addr {
s.routines.Add(1)
go s.runPort(addr, false) go s.runPort(addr, false)
} }
if err != http.ErrServerClosed {
log.Error().Err(err).Msg("Server unexpected exited")
}
s.routines.Wait() s.routines.Wait()
} }
@ -145,24 +164,27 @@ func (s *Server) DomainRouter(w http.ResponseWriter, r *http.Request) {
log.Trace().Strs(header, value).Msg("Headers") log.Trace().Strs(header, value).Msg("Headers")
} }
if router, found := s.mrouter[domain]; found { if router, found := s.mrouter[domain]; found {
log.Info().Bool("isnil", router == nil).Str("domain", domain).Msg("Debuginfo")
router.NoMethod(gin.WrapH(s.NotFoundHandler)) router.NoMethod(gin.WrapH(s.NotFoundHandler))
router.NoRoute(gin.WrapH(s.NotFoundHandler)) router.NoRoute(gin.WrapH(s.NotFoundHandler))
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
return return
} }
log.Error().Msgf("Failed to find domain for %s", domain)
var entrys []string var entrys []string
for d := range s.mrouter { for d := range s.mrouter {
entrys = append(entrys, d) entrys = append(entrys, d)
} }
log.Trace().Strs("reqistred domains", entrys).Msg("domain not found") log.Trace().Strs("registred domains", entrys).Msg("domain not found")
s.NotFoundHandler.ServeHTTP(w, r) s.NotFoundHandler.ServeHTTP(w, r)
} }
// Use installs the middleware into the router. // Use installs the middleware into the router.
// The Middleware must be able to detect multiple calls byy itself. Deduplication is not performed. // The Middleware must be able to detect multiple calls byy itself. Deduplication is not performed.
func (s *Server) Use(m ...gin.HandlerFunc) { func (s *Server) Use(m ...gin.HandlerFunc) {
for _, router := range s.mrouter { s.middleware = append(s.middleware, m...)
router.Use(m...) for _, site := range s.mrouter {
site.Use(m...)
} }
} }
@ -178,37 +200,37 @@ func (s *Server) menus() []menus.Menu {
return s.menu return s.menu
} }
// Setup ets the server up. It loads the sites and prepare the server for startup. // Setup sets the server up. It loads the sites and prepare the server for startup.
// The sites get their config in this step. // The sites get their config in this step.
func (s *Server) Setup() { func (s *Server) Setup() {
var router *gin.Engine var router *gin.Engine
var found bool var found bool
for cfg, site := range s.sites { for cfg, site := range s.sites {
config := s.Conf.Sites[cfg] config := s.Conf.Sites[cfg]
if router, found = s.mrouter[config.Domain()]; !found { if router, found = s.mrouter[config["domain"].(string)]; !found {
router = gin.New() router = gin.New()
mw := []gin.HandlerFunc{ router.Use(func(c *gin.Context) {
func(c *gin.Context) { c.Set(Domain, config["domain"])
c.Set(Menus, s.menus) c.Set(Menus, s.menus)
c.Set(Domain, config.Domain()) c.Set(Accounts, s.authh.Account(c))
}, })
router.Use(s.middleware...)
s.mrouter[config["domain"].(string)] = router
} }
mw = append(mw, func(c *gin.Context) { s.authh.Account(c) }) group := router.Group(config["path"].(string))
router.Use(mw...) site.Init(group)
s.mrouter[config.Domain()] = router
}
site.Init(router.Group(config.Path()))
if ms, ok := site.(menus.MenuSite); ok { if ms, ok := site.(menus.MenuSite); ok {
menus := ms.Menu(config.Domain()) menus := ms.Menu(config["domain"].(string))
log.Debug().Msgf("%d menus are added", len(menus)) log.Debug().Msgf("%d menus are added", len(menus))
s.menu = append(s.menu, menus...) s.menu = append(s.menu, menus...)
} }
if ts, ok := site.(templates.TemplateSite); ok { if ts, ok := site.(templates.TemplateSite); ok {
templates := ts.Templates() templates := ts.Templates()
log.Debug().Msgf("Templates for %s%s are added", config.Domain(), config.Path()) log.Debug().Msgf("Templates for %s%s are added", config["domain"], config["path"].(string))
s.template.AddParseTree(templates.Name(), templates.Tree) s.template.AddParseTree(templates.Name(), templates.Tree)
s.template.Funcs(ts.Funcs()) s.template.Funcs(ts.Funcs())
} }
site.Setup(config)
} }
s.setup = true s.setup = true
} }
@ -217,6 +239,12 @@ func (s *Server) Setup() {
// it registers the defaults so that the application can load/dump it from/into an configfile or commandline options // it registers the defaults so that the application can load/dump it from/into an configfile or commandline options
func (s *Server) RegisterSite(cfg string, site Site) { func (s *Server) RegisterSite(cfg string, site Site) {
var config = site.Defaults() var config = site.Defaults()
if _, found := config["domain"]; !found {
config["domain"] = ""
}
if _, found := config["path"]; !found {
config["path"] = ""
}
s.Conf.Sites[cfg] = config s.Conf.Sites[cfg] = config
s.sites[cfg] = site s.sites[cfg] = site
} }

Datei anzeigen

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"path"
"time" "time"
"github.com/crewjam/saml" "github.com/crewjam/saml"
@ -29,7 +30,7 @@ func musturi(url *url.URL, err error) *url.URL {
var ( var (
defaultsaml = &SAML{ defaultsaml = &SAML{
idp: musturi(url.ParseRequestURI("https://samltest.id/saml/idp")), idp: musturi(url.ParseRequestURI("https://samltest.id/saml/idp")),
SiteDomain: "example.com", Domain: "example.com",
Cookiename: "ILOVECOOKIES", Cookiename: "ILOVECOOKIES",
} }
_ httpserver.Site = defaultsaml _ httpserver.Site = defaultsaml
@ -41,7 +42,6 @@ type metadata struct{}
type SAML struct { type SAML struct {
router *gin.RouterGroup router *gin.RouterGroup
publicroot string publicroot string
Keyfiles []string
SPPublicKey string SPPublicKey string
sppublickey *x509.Certificate sppublickey *x509.Certificate
SPPrivatekey string SPPrivatekey string
@ -51,13 +51,40 @@ type SAML struct {
idp *url.URL idp *url.URL
sp *saml.ServiceProvider sp *saml.ServiceProvider
HTTPClient http.Client `toml:"-"` HTTPClient http.Client `toml:"-"`
SiteDomain string `toml:"domain"` Domain string `toml:"domain"`
SitePath string `toml:"path"`
Cookiename string Cookiename string
} }
// Setup sets the saml object up. // Setup sets the saml object up.
func (s *SAML) Setup(config httpserver.SiteConfig) (err error) { func (s *SAML) Setup(config httpserver.SiteConfig) (err error) {
log.Info().Msg("Setting up SAML service provider")
s.Domain = config["domain"].(string)
s.sp = &saml.ServiceProvider{
AcsURL: url.URL{
Scheme: "https",
Host: s.Domain,
Path: path.Join(s.publicroot, "acs"),
},
MetadataURL: url.URL{
Scheme: "https",
Host: s.Domain,
Path: path.Join(s.publicroot, "metadata.xml"),
},
}
switch config["metadatavalid"].(type) {
case time.Duration:
s.sp.MetadataValidDuration = config["metadatavalid"].(time.Duration)
case int:
s.sp.MetadataValidDuration = time.Duration(config["metadatavalid"].(int))
case int8:
s.sp.MetadataValidDuration = time.Duration(config["metadatavalid"].(int8))
case int16:
s.sp.MetadataValidDuration = time.Duration(config["metadatavalid"].(int16))
case int32:
s.sp.MetadataValidDuration = time.Duration(config["metadatavalid"].(int32))
case int64:
s.sp.MetadataValidDuration = time.Duration(config["metadatavalid"].(int64))
}
var key interface{} var key interface{}
if keyfile, found := config["spprivatekey"]; found { if keyfile, found := config["spprivatekey"]; found {
s.SPPrivatekey = keyfile.(string) s.SPPrivatekey = keyfile.(string)
@ -123,26 +150,23 @@ func (s *SAML) Setup(config httpserver.SiteConfig) (err error) {
func (s *SAML) Init(router *gin.RouterGroup) { func (s *SAML) Init(router *gin.RouterGroup) {
s.publicroot = router.BasePath() s.publicroot = router.BasePath()
s.router = router s.router = router
s.sp.AcsURL = url.URL{ router.GET("metadata.xml", s.metadataHF)
Scheme: "https", router.POST("acs", s.acsHF)
Host: s.SiteDomain,
Path: s.publicroot + "/acs",
}
s.sp.MetadataURL = url.URL{
Scheme: "https",
Host: s.SiteDomain,
Path: s.publicroot + "/metadata.xml",
}
router.GET("/metadata.xml", s.metadataHF)
router.POST("/acs", s.acsHF)
} }
// Teardown is to satisfy the httpserver.Site interface. // Teardown is to satisfy the httpserver.Site interface.
func (s *SAML) Teardown() {} func (s *SAML) Teardown() {}
func (s *SAML) metadataHF(c *gin.Context) { func (s *SAML) metadataHF(c *gin.Context) {
if s.sp == nil {
c.AbortWithStatus(500)
return
}
m := s.sp.Metadata() m := s.sp.Metadata()
log.Debug().Time("Validuntil", m.ValidUntil).Msg("SP Metadata") if m == nil {
c.AbortWithStatus(500)
return
}
c.XML(http.StatusOK, m) c.XML(http.StatusOK, m)
} }
@ -181,18 +205,14 @@ func (s *SAML) acsHF(c *gin.Context) {
c.Redirect(http.StatusSeeOther, redirect) c.Redirect(http.StatusSeeOther, redirect)
} }
// Domain returns an the configured domain
func (s *SAML) Domain() string {
return s.SiteDomain
}
// Defaults returns the default values for the config // Defaults returns the default values for the config
func (s *SAML) Defaults() httpserver.SiteConfig { func (s *SAML) Defaults() httpserver.SiteConfig {
return map[string]interface{}{ return map[string]interface{}{
"domain": "example.com", "domain": "example.com",
"idp": defaultsaml.idp, "idp": defaultsaml.idp.String(),
"sppublickey": "publickey.pem", "sppublickey": "publickey.pem",
"spprivatekey": "privatekey.pem", "spprivatekey": "privatekey.pem",
"jwtprivatekey": "privatekey.pem", "jwtprivatekey": "privatekey.pem",
"metadatavalid": time.Duration(time.Hour * 24),
} }
} }

10
site.go
Datei anzeigen

@ -13,13 +13,3 @@ type Site interface {
// SiteConfig is an interface for configitems of the site. The methods return the required items for the server // SiteConfig is an interface for configitems of the site. The methods return the required items for the server
type SiteConfig map[string]interface{} type SiteConfig map[string]interface{}
// Domain gives an easier access to the domain value
func (sc SiteConfig) Domain() string {
return sc["domain"].(string)
}
// Path gives an easier access to the path value
func (sc SiteConfig) Path() string {
return sc["path"].(string)
}