emissary/internal/agent/controller/app/server.go

197 lines
4.2 KiB
Go
Raw Normal View History

package app
import (
"context"
"net/http"
"strings"
2023-03-13 10:44:58 +01:00
"sync"
2023-03-28 20:43:45 +02:00
"time"
2023-03-28 20:43:45 +02:00
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/Cadoles/emissary/internal/proxy/wildcard"
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
2023-03-21 15:21:19 +01:00
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
2023-03-13 10:44:58 +01:00
"gitlab.com/wpetit/goweb/logger"
"forge.cadoles.com/arcad/edge/pkg/bundle"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/chi/v5"
2023-03-21 15:21:19 +01:00
"github.com/lestrrat-go/jwx/v2/jwa"
2023-03-21 13:28:41 +01:00
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/pkg/errors"
2023-03-21 15:21:19 +01:00
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd"
)
2023-03-28 20:43:45 +02:00
const defaultCookieDuration time.Duration = 24 * time.Hour
type Server struct {
2023-03-28 20:43:45 +02:00
bundle bundle.Bundle
handlerOptions []edgeHTTP.HandlerOptionFunc
server *http.Server
serverMutex sync.RWMutex
auth *appSpec.Auth
}
2023-03-13 10:44:58 +01:00
func (s *Server) Start(ctx context.Context, addr string) (err error) {
if s.server != nil {
if err := s.Stop(); err != nil {
return errors.WithStack(err)
}
}
router := chi.NewRouter()
router.Use(middleware.Logger)
2023-03-28 20:43:45 +02:00
handler := edgeHTTP.NewHandler(s.handlerOptions...)
if err := handler.Load(s.bundle); err != nil {
return errors.Wrap(err, "could not load app bundle")
}
2023-03-28 20:43:45 +02:00
if err := s.configureAuth(router, s.auth); err != nil {
return errors.WithStack(err)
2023-03-21 15:21:19 +01:00
}
router.Handle("/*", handler)
server := &http.Server{
Addr: addr,
Handler: router,
}
go func() {
2023-03-13 10:44:58 +01:00
defer func() {
if recovered := recover(); recovered != nil {
if err, ok := recovered.(error); ok {
logger.Error(ctx, err.Error(), logger.E(errors.WithStack(err)))
return
}
panic(recovered)
}
}()
defer func() {
if err := s.Stop(); err != nil {
panic(errors.WithStack(err))
}
}()
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(errors.WithStack(err))
}
}()
2023-03-13 10:44:58 +01:00
s.serverMutex.Lock()
s.server = server
2023-03-13 10:44:58 +01:00
s.serverMutex.Unlock()
return nil
}
2023-03-13 10:44:58 +01:00
func (s *Server) Running() bool {
s.serverMutex.RLock()
defer s.serverMutex.RUnlock()
return s.server != nil
}
func (s *Server) Stop() error {
if s.server == nil {
return nil
}
defer func() {
2023-03-13 10:44:58 +01:00
s.serverMutex.Lock()
s.server = nil
2023-03-13 10:44:58 +01:00
s.serverMutex.Unlock()
}()
if err := s.server.Close(); err != nil {
return errors.WithStack(err)
}
return nil
}
2023-03-28 20:43:45 +02:00
func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error {
if auth == nil {
return nil
}
2023-03-22 18:15:22 +01:00
2023-03-28 20:43:45 +02:00
switch {
case auth.Local != nil:
var rawKey any = s.auth.Local.Key
if strKey, ok := rawKey.(string); ok {
rawKey = []byte(strKey)
}
2023-03-22 18:15:22 +01:00
2023-03-28 20:43:45 +02:00
key, err := jwk.FromRaw(rawKey)
if err != nil {
return errors.WithStack(err)
}
2023-03-13 10:44:58 +01:00
2023-03-28 20:43:45 +02:00
cookieDuration := defaultCookieDuration
if s.auth.Local.CookieDuration != "" {
cookieDuration, err = time.ParseDuration(s.auth.Local.CookieDuration)
if err != nil {
return errors.WithStack(err)
}
}
if s.auth.Local.CookieDomain != "" {
router.Use(invalidCookieDomainRedirect(s.auth.Local.CookieDomain))
}
2023-03-28 20:43:45 +02:00
router.Handle("/auth/*", authHTTP.NewLocalHandler(
jwa.HS256, key,
authHTTP.WithRoutePrefix("/auth"),
authHTTP.WithAccounts(s.auth.Local.Accounts...),
authHTTP.WithCookieOptions(s.auth.Local.CookieDomain, cookieDuration),
))
}
2023-03-28 20:43:45 +02:00
return nil
2023-03-21 13:28:41 +01:00
}
2023-03-28 20:43:45 +02:00
func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server {
return &Server{
2023-03-28 20:43:45 +02:00
bundle: bundle,
auth: auth,
handlerOptions: handlerOptions,
}
}
func invalidCookieDomainRedirect(cookieDomain string) func(http.Handler) http.Handler {
domain := strings.TrimPrefix(cookieDomain, ".")
hostPattern := "*" + domain
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
hostParts := strings.SplitN(r.Host, ":", 2)
if !wildcard.Match(hostParts[0], hostPattern) {
url := r.URL
newHost := domain
if len(hostParts) > 1 {
newHost += ":" + hostParts[1]
}
url.Host = newHost
http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}