Skip to content

Commit

Permalink
Merge pull request #15 from hvuhsg/autotls
Browse files Browse the repository at this point in the history
Autotls
  • Loading branch information
hvuhsg authored Oct 22, 2024
2 parents 6494b44 + beadd06 commit c530f23
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 331 deletions.
62 changes: 59 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,44 @@ func (s Service) validate() error {
}

type TLS struct {
KeyFile *string `yaml:"keyfile"`
CertFile *string `yaml:"certfile"`
Auto bool `yaml:"auto"`
Domains []string `yaml:"domain"`
Email *string `yaml:"email"`
KeyFile *string `yaml:"keyfile"`
CertFile *string `yaml:"certfile"`
}

func (tls TLS) validate() error {
if tls.Auto {
if len(tls.Domains) == 0 {
return errors.New("when using the auto tls feature you MUST include a list of domains to issue certificates for")
}
if tls.Email == nil || len(*tls.Email) == 0 || !isValidEmail(*tls.Email) {
return errors.New("when using the auto tls feature you MUST include a valid email for the lets-encrypt registration")
}
}

if tls.CertFile != nil {
if tls.KeyFile == nil {
return errors.New("you MUST provide certfile AND keyfile")
}
}

if tls.KeyFile != nil {
if tls.CertFile == nil {
return errors.New("you MUST provide certfile AND keyfile")
}

if !isValidFile(*tls.CertFile) {
return errors.New("certfile path is invalid")
}

if !isValidFile(*tls.KeyFile) {
return errors.New("keyfile path is invalid")
}
}

return nil
}

type OTEL struct {
Expand Down Expand Up @@ -216,7 +252,7 @@ type Config struct {
OTEL *OTEL `yaml:"open_telemetry"`

// TLS options
SSL TLS `yaml:"ssl"`
TLS TLS `yaml:"ssl"`

Services []Service `yaml:"services"`
}
Expand Down Expand Up @@ -246,6 +282,18 @@ func (c Config) Validate(currentVersion string) error {
}
}

if c.Port == 0 {
return errors.New("port is required")
}

if err := c.TLS.validate(); err != nil {
return err
}

if c.TLS.Auto && c.Port != 443 {
return errors.New("the auto tls feature is only available if the server runs on port 443")
}

for _, service := range c.Services {
if err := service.validate(); err != nil {
return err
Expand Down Expand Up @@ -405,3 +453,11 @@ func isValidGRPCAddress(address string) error {

return nil
}

func isValidEmail(email string) bool {
// Define a regular expression for valid email addresses
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

// Match the email string with the regular expression
return emailRegex.MatchString(email)
}
3 changes: 2 additions & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ func TestConfigValidate(t *testing.T) {
currentVersion string
wantErr bool
}{
{"Valid config", Config{Version: "1.0.0", Host: "localhost", Services: []Service{{Domain: "example.com", Paths: []Path{{Path: "/api", Destination: ptr("http://api.example.com")}}}}}, "1.0.0", false},
{"Valid config", Config{Version: "1.0.0", Host: "localhost", Port: 80, Services: []Service{{Domain: "example.com", Paths: []Path{{Path: "/api", Destination: ptr("http://api.example.com")}}}}}, "1.0.0", false},
{"AutoTLS with port != 443", Config{Version: "1.0.0", Host: "localhost", Port: 80, TLS: TLS{Auto: true, Domains: []string{"example.com"}}, Services: []Service{{Domain: "example.com", Paths: []Path{{Path: "/api", Destination: ptr("http://api.example.com")}}}}}, "1.0.0", true},
{"Missing version", Config{Host: "localhost"}, "1.0.0", true},
{"Invalid version", Config{Version: "invalid", Host: "localhost"}, "1.0.0", true},
{"Future version", Config{Version: "2.0.0", Host: "localhost"}, "1.0.0", true},
Expand Down
103 changes: 2 additions & 101 deletions gatego.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ package gatego

import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"time"

"github.com/hvuhsg/gatego/config"
"github.com/hvuhsg/gatego/contextvalues"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

const serviceName = "gatego"
Expand Down Expand Up @@ -48,15 +42,13 @@ func (gg GateGo) Run() error {
checker := createChecker(gg.config.Services)
checker.Start()

table, err := NewHandlersTable(gg.ctx, useOtel, gg.config.Services)
server, err := newServer(gg.ctx, gg.config, useOtel)
if err != nil {
return err
}

server := gg.createServer(table)
defer server.Shutdown(gg.ctx)

serveErrChan, err := serve(server, gg.config.SSL.CertFile, gg.config.SSL.KeyFile)
serveErrChan, err := server.serve(gg.config.TLS.CertFile, gg.config.TLS.KeyFile)
if err != nil {
return err
}
Expand Down Expand Up @@ -92,94 +84,3 @@ func createChecker(services []config.Service) *Checker {

return checker
}

func (gg GateGo) createServer(table HandlerTable) *http.Server {
mux := http.NewServeMux()

// handleFunc is a replacement for mux.HandleFunc
// which enriches the handler's HTTP instrumentation with the pattern as the http.route.
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
// Configure the "http.route" for the HTTP instrumentation.
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
mux.Handle(pattern, handler)
}

handleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handler := table.GetHandler(r.Host, r.URL.Path)

if handler == nil {
w.WriteHeader(http.StatusNotFound)
return
}

handler.ServeHTTP(w, r)
})

// Add HTTP instrumentation for the whole server.
handler := otelhttp.NewHandler(mux, "/")

addr := fmt.Sprintf("%s:%d", gg.config.Host, gg.config.Port)

// Start HTTP server.
server := &http.Server{
Addr: addr,
BaseContext: func(_ net.Listener) context.Context { return gg.ctx },
ReadTimeout: time.Second,
WriteTimeout: 10 * time.Second,
Handler: handler,
}

return server
}

func serve(server *http.Server, certfile *string, keyfile *string) (chan error, error) {
supportTLS, err := checkTLSConfig(certfile, keyfile)
if err != nil {
return nil, err
}

serveErr := make(chan error, 1)

go func() {
if supportTLS {
log.Default().Printf("Serving proxy with TLS %s\n", server.Addr)
serveErr <- server.ListenAndServeTLS(*certfile, *keyfile)
} else {
log.Default().Printf("Serving proxy %s\n", server.Addr)
serveErr <- server.ListenAndServe()
}
}()

return serveErr, nil
}

func checkTLSConfig(certfile *string, keyfile *string) (bool, error) {
if keyfile == nil || certfile == nil || *keyfile == "" || *certfile == "" {
return false, nil
}

if !fileExists(*keyfile) {
return false, fmt.Errorf("can't find keyfile at '%s'", *keyfile)
}

if !fileExists(*certfile) {
return false, fmt.Errorf("can't find certfile at '%s'", *certfile)
}

return true, nil
}

func fileExists(filepath string) bool {
_, err := os.Stat(filepath)

if os.IsNotExist(err) {
return false
}

// If we cant check the file info we probably can't open the file
if err != nil {
return false
}

return true
}
63 changes: 0 additions & 63 deletions handlertable.go

This file was deleted.

56 changes: 56 additions & 0 deletions pkg/multimux/multimux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// This package implement a mutil-mux an http handler
// that acts as seprate http.ServeMux for each registred host

package multimux

import (
"net/http"
"strings"
)

type MultiMux struct {
Hosts map[string]*http.ServeMux
}

func NewMultiMux() *MultiMux {
hosts := make(map[string]*http.ServeMux)
return &MultiMux{Hosts: hosts}
}

func (mm *MultiMux) RegisterHandler(host string, pattern string, handler http.Handler) {
cleanedHost := cleanHost(host)
mux, exists := mm.Hosts[cleanedHost]

if !exists {
mux = http.NewServeMux()
mm.Hosts[cleanedHost] = mux
}

cleanedPattern := strings.ToLower(pattern)

mux.Handle(cleanedPattern, handler)
}

func (mm *MultiMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host := r.Host
cleanedHost := cleanHost(host)
mux, exists := mm.Hosts[cleanedHost]

if !exists {
w.WriteHeader(http.StatusNotFound)
return
}

mux.ServeHTTP(w, r)
}

func cleanHost(domain string) string {
return removePort(strings.ToLower(domain))
}

func removePort(addr string) string {
if i := strings.LastIndex(addr, ":"); i != -1 {
return addr[:i]
}
return addr
}
Loading

0 comments on commit c530f23

Please sign in to comment.