Skip to content

Commit

Permalink
Begin work on dynamic https/http
Browse files Browse the repository at this point in the history
  • Loading branch information
NHAS committed Nov 26, 2024
1 parent 8563ec4 commit e944359
Show file tree
Hide file tree
Showing 16 changed files with 752 additions and 649 deletions.
271 changes: 106 additions & 165 deletions adminui/ui_webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package adminui

import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
Expand All @@ -16,6 +15,7 @@ import (

"github.com/NHAS/session"
"github.com/NHAS/wag/adminui/frontend"
"github.com/NHAS/wag/internal/autotls"
"github.com/NHAS/wag/internal/config"
"github.com/NHAS/wag/internal/data"
"github.com/NHAS/wag/internal/router"
Expand All @@ -37,8 +37,6 @@ type AdminUI struct {

logQueue *queue.Queue[[]byte]

https, http *http.Server

listenerEvents struct {
clusterHealth string
}
Expand Down Expand Up @@ -160,176 +158,125 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)

log.SetOutput(io.MultiWriter(os.Stdout, adminUI.logQueue))

//https://blog.cloudflare.com/exposing-go-on-the-internet/
tlsConfig := &tls.Config{
// Only use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519, // Go 1.8 only
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}

go func() {

protectedRoutes := http.NewServeMux()
allRoutes := http.NewServeMux()
protectedRoutes := http.NewServeMux()
allRoutes := http.NewServeMux()

allRoutes.HandleFunc("/", frontend.Index)
allRoutes.HandleFunc("GET /index.html", frontend.Index)
allRoutes.HandleFunc("/", frontend.Index)
allRoutes.HandleFunc("GET /index.html", frontend.Index)

allRoutes.HandleFunc("GET /favicon.ico", frontend.Favicon)
allRoutes.HandleFunc("GET /logo.png", frontend.Logo)
allRoutes.HandleFunc("GET /assets/", frontend.Assets)
allRoutes.HandleFunc("GET /favicon.ico", frontend.Favicon)
allRoutes.HandleFunc("GET /logo.png", frontend.Logo)
allRoutes.HandleFunc("GET /assets/", frontend.Assets)

allRoutes.HandleFunc("POST /api/login", adminUI.doLogin)
allRoutes.HandleFunc("GET /api/config", adminUI.uiConfig)
allRoutes.HandleFunc("POST /api/refresh", adminUI.doAuthRefresh)
allRoutes.HandleFunc("POST /api/login", adminUI.doLogin)
allRoutes.HandleFunc("GET /api/config", adminUI.uiConfig)
allRoutes.HandleFunc("POST /api/refresh", adminUI.doAuthRefresh)

if config.Values.ManagementUI.OIDC.Enabled {
allRoutes.HandleFunc("GET /login/oidc", func(w http.ResponseWriter, r *http.Request) {
rp.AuthURLHandler(func() string {
r, _ := utils.GenerateRandomHex(32)
return r
}, adminUI.oidcProvider)(w, r)
})
if config.Values.ManagementUI.OIDC.Enabled {
allRoutes.HandleFunc("GET /login/oidc", func(w http.ResponseWriter, r *http.Request) {
rp.AuthURLHandler(func() string {
r, _ := utils.GenerateRandomHex(32)
return r
}, adminUI.oidcProvider)(w, r)
})

allRoutes.HandleFunc("GET /login/oidc/callback", adminUI.oidcCallback)
}
allRoutes.HandleFunc("GET /login/oidc/callback", adminUI.oidcCallback)
}

allRoutes.Handle("/api/", adminUI.sessionManager.AuthorisationChecks(protectedRoutes,
func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
},
func(w http.ResponseWriter, r *http.Request, dAdmin data.AdminUserDTO) bool {

key, adminDetails := adminUI.sessionManager.GetSessionFromRequest(r)
if adminDetails != nil {
if adminDetails.Type == "" || adminDetails.Type == data.LocalUser {
d, err := data.GetAdminUser(dAdmin.Username)
if err != nil {
adminUI.sessionManager.DeleteSession(w, r)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return false
}

adminUI.sessionManager.UpdateSession(key, d)
allRoutes.Handle("/api/", adminUI.sessionManager.AuthorisationChecks(protectedRoutes,
func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
},
func(w http.ResponseWriter, r *http.Request, dAdmin data.AdminUserDTO) bool {

key, adminDetails := adminUI.sessionManager.GetSessionFromRequest(r)
if adminDetails != nil {
if adminDetails.Type == "" || adminDetails.Type == data.LocalUser {
d, err := data.GetAdminUser(dAdmin.Username)
if err != nil {
adminUI.sessionManager.DeleteSession(w, r)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return false
}

// Otherwise the admin type is OIDC, and will no be in the local db
adminUI.sessionManager.UpdateSession(key, d)
}

return true
}))

protectedRoutes.HandleFunc("GET /api/info", adminUI.serverInfo)
protectedRoutes.HandleFunc("GET /api/console_log", adminUI.consoleLog)

protectedRoutes.HandleFunc("GET /api/cluster/members", adminUI.members)
protectedRoutes.HandleFunc("POST /api/cluster/members", adminUI.newNode)
protectedRoutes.HandleFunc("PUT /api/cluster/members", adminUI.nodeControl)

protectedRoutes.HandleFunc("GET /api/cluster/events", adminUI.getClusterEvents)
protectedRoutes.HandleFunc("PUT /api/cluster/events", adminUI.clusterEventsAcknowledge)

protectedRoutes.HandleFunc("GET /api/diag/wg", adminUI.wgDiagnositicsData)
protectedRoutes.HandleFunc("GET /api/diag/firewall", adminUI.getFirewallState)
protectedRoutes.HandleFunc("POST /api/diag/check", adminUI.firewallCheckTest)
protectedRoutes.HandleFunc("POST /api/diag/acls", adminUI.aclsTest)
protectedRoutes.HandleFunc("POST /api/diag/notifications", adminUI.testNotifications)

protectedRoutes.HandleFunc("GET /api/management/users", adminUI.getUsers)
protectedRoutes.HandleFunc("PUT /api/management/users", adminUI.editUser)
protectedRoutes.HandleFunc("DELETE /api/management/users", adminUI.removeUsers)
protectedRoutes.HandleFunc("GET /api/management/admin_users", adminUI.adminUsersData)

protectedRoutes.HandleFunc("GET /api/management/devices", adminUI.getAllDevices)
protectedRoutes.HandleFunc("PUT /api/management/devices", adminUI.editDevice)
protectedRoutes.HandleFunc("DELETE /api/management/devices", adminUI.deleteDevice)

protectedRoutes.HandleFunc("GET /api/management/registration_tokens", adminUI.getAllRegistrationTokens)
protectedRoutes.HandleFunc("POST /api/management/registration_tokens", adminUI.createRegistrationToken)
protectedRoutes.HandleFunc("DELETE /api/management/registration_tokens", adminUI.deleteRegistrationTokens)

protectedRoutes.HandleFunc("GET /api/policy/rules", adminUI.getAllPolicies)
protectedRoutes.HandleFunc("PUT /api/policy/rules", adminUI.editPolicy)
protectedRoutes.HandleFunc("POST /api/policy/rules", adminUI.createPolicy)
protectedRoutes.HandleFunc("DELETE /api/policy/rules", adminUI.deletePolices)

protectedRoutes.HandleFunc("GET /api/policy/groups", adminUI.getAllGroups)
protectedRoutes.HandleFunc("PUT /api/policy/groups", adminUI.editGroup)
protectedRoutes.HandleFunc("POST /api/policy/groups", adminUI.createGroup)
protectedRoutes.HandleFunc("DELETE /api/policy/groups", adminUI.deleteGroups)

protectedRoutes.HandleFunc("PUT /api/settings/general", adminUI.updateGeneralSettings)
protectedRoutes.HandleFunc("PUT /api/settings/login", adminUI.updateLoginSettings)
protectedRoutes.HandleFunc("GET /api/settings/general", adminUI.getGeneralSettings)
protectedRoutes.HandleFunc("GET /api/settings/login", adminUI.getLoginSettings)
protectedRoutes.HandleFunc("GET /api/settings/all_mfa_methods", adminUI.getAllMfaMethods)

notifications := make(chan NotificationDTO, 1)
protectedRoutes.HandleFunc("GET /api/notifications", adminUI.notificationsWS(notifications))
data.RegisterEventListener(data.NodeErrors, true, adminUI.receiveErrorNotifications(notifications))
go adminUI.monitorClusterMembers(notifications)

should, err := data.ShouldCheckUpdates()
if err == nil && should {
adminUI.startUpdateChecker(notifications)
}

protectedRoutes.HandleFunc("PUT /api/change_password", adminUI.changePassword)

protectedRoutes.HandleFunc("GET /api/logout", func(w http.ResponseWriter, r *http.Request) {
adminUI.sessionManager.DeleteSession(w, r)
w.WriteHeader(http.StatusNoContent)
})

protectedRoutes.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})

if data.SupportsTLS(data.ManagementUI) {
// Otherwise the admin type is OIDC, and will no be in the local db
}

go func() {
return true
}))

protectedRoutes.HandleFunc("GET /api/info", adminUI.serverInfo)
protectedRoutes.HandleFunc("GET /api/console_log", adminUI.consoleLog)

protectedRoutes.HandleFunc("GET /api/cluster/members", adminUI.members)
protectedRoutes.HandleFunc("POST /api/cluster/members", adminUI.newNode)
protectedRoutes.HandleFunc("PUT /api/cluster/members", adminUI.nodeControl)

protectedRoutes.HandleFunc("GET /api/cluster/events", adminUI.getClusterEvents)
protectedRoutes.HandleFunc("PUT /api/cluster/events", adminUI.clusterEventsAcknowledge)

protectedRoutes.HandleFunc("GET /api/diag/wg", adminUI.wgDiagnositicsData)
protectedRoutes.HandleFunc("GET /api/diag/firewall", adminUI.getFirewallState)
protectedRoutes.HandleFunc("POST /api/diag/check", adminUI.firewallCheckTest)
protectedRoutes.HandleFunc("POST /api/diag/acls", adminUI.aclsTest)
protectedRoutes.HandleFunc("POST /api/diag/notifications", adminUI.testNotifications)

protectedRoutes.HandleFunc("GET /api/management/users", adminUI.getUsers)
protectedRoutes.HandleFunc("PUT /api/management/users", adminUI.editUser)
protectedRoutes.HandleFunc("DELETE /api/management/users", adminUI.removeUsers)
protectedRoutes.HandleFunc("GET /api/management/admin_users", adminUI.adminUsersData)

protectedRoutes.HandleFunc("GET /api/management/devices", adminUI.getAllDevices)
protectedRoutes.HandleFunc("PUT /api/management/devices", adminUI.editDevice)
protectedRoutes.HandleFunc("DELETE /api/management/devices", adminUI.deleteDevice)

protectedRoutes.HandleFunc("GET /api/management/registration_tokens", adminUI.getAllRegistrationTokens)
protectedRoutes.HandleFunc("POST /api/management/registration_tokens", adminUI.createRegistrationToken)
protectedRoutes.HandleFunc("DELETE /api/management/registration_tokens", adminUI.deleteRegistrationTokens)

protectedRoutes.HandleFunc("GET /api/policy/rules", adminUI.getAllPolicies)
protectedRoutes.HandleFunc("PUT /api/policy/rules", adminUI.editPolicy)
protectedRoutes.HandleFunc("POST /api/policy/rules", adminUI.createPolicy)
protectedRoutes.HandleFunc("DELETE /api/policy/rules", adminUI.deletePolices)

protectedRoutes.HandleFunc("GET /api/policy/groups", adminUI.getAllGroups)
protectedRoutes.HandleFunc("PUT /api/policy/groups", adminUI.editGroup)
protectedRoutes.HandleFunc("POST /api/policy/groups", adminUI.createGroup)
protectedRoutes.HandleFunc("DELETE /api/policy/groups", adminUI.deleteGroups)

protectedRoutes.HandleFunc("PUT /api/settings/general", adminUI.updateGeneralSettings)
protectedRoutes.HandleFunc("PUT /api/settings/login", adminUI.updateLoginSettings)
protectedRoutes.HandleFunc("GET /api/settings/general", adminUI.getGeneralSettings)
protectedRoutes.HandleFunc("GET /api/settings/login", adminUI.getLoginSettings)
protectedRoutes.HandleFunc("GET /api/settings/all_mfa_methods", adminUI.getAllMfaMethods)

notifications := make(chan NotificationDTO, 1)
protectedRoutes.HandleFunc("GET /api/notifications", adminUI.notificationsWS(notifications))
data.RegisterEventListener(data.NodeErrors, true, adminUI.receiveErrorNotifications(notifications))
go adminUI.monitorClusterMembers(notifications)

should, err := data.ShouldCheckUpdates()
if err == nil && should {
adminUI.startUpdateChecker(notifications)
}

adminUI.https = &http.Server{
Addr: config.Values.ManagementUI.ListenAddress,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
TLSConfig: tlsConfig,
Handler: utils.SetSecurityHeaders(allRoutes),
}
protectedRoutes.HandleFunc("PUT /api/change_password", adminUI.changePassword)

if err := adminUI.https.ListenAndServeTLS("", ""); err != nil && !errors.Is(err, http.ErrServerClosed) {
errs <- fmt.Errorf("TLS management listener failed: %v", err)
}
protectedRoutes.HandleFunc("GET /api/logout", func(w http.ResponseWriter, r *http.Request) {
adminUI.sessionManager.DeleteSession(w, r)
w.WriteHeader(http.StatusNoContent)
})

}()
} else {
go func() {
adminUI.http = &http.Server{
Addr: config.Values.ManagementUI.ListenAddress,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
Handler: utils.SetSecurityHeaders(allRoutes),
}
if err := adminUI.http.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errs <- fmt.Errorf("webserver management listener failed: %v", adminUI.http.ListenAndServe())
}
protectedRoutes.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})

}()
}
}()
if err := autotls.Do.DynamicListener(data.Management, utils.SetSecurityHeaders(allRoutes)); err != nil {
return nil, err
}

log.Println("[ADMINUI] Started Managemnt UI listening:", config.Values.ManagementUI.ListenAddress)

Expand Down Expand Up @@ -471,13 +418,7 @@ func (au *AdminUI) oidcCallback(w http.ResponseWriter, r *http.Request) {

func (au *AdminUI) Close() {

if au.http != nil {
au.http.Close()
}

if au.https != nil {
au.https.Close()
}
autotls.Do.Close(data.Management)

if config.Values.ManagementUI.Enabled {
log.Println("Stopped Management UI")
Expand Down
7 changes: 6 additions & 1 deletion commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"syscall"

"github.com/NHAS/wag/adminui"
"github.com/NHAS/wag/internal/autotls"
"github.com/NHAS/wag/internal/config"
"github.com/NHAS/wag/internal/data"
"github.com/NHAS/wag/internal/enrolment"
Expand Down Expand Up @@ -73,9 +74,13 @@ func (g *start) Check() error {

err := data.Load(config.Values.DatabaseLocation, g.clusterJoinToken, false)
if err != nil {
return fmt.Errorf("cannot load database: %v", err)
return fmt.Errorf("cannot load database: %w", err)
}

err = autotls.Initialise()
if err != nil {
return fmt.Errorf("failed to initialise auto tls module: %w", err)
}
return nil

}
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.15.0 // indirect
github.com/caddyserver/certmagic v0.21.4 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.108.0 // indirect
Expand Down Expand Up @@ -68,9 +70,12 @@ require (
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mholt/acmez/v2 v2.0.3 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand All @@ -87,6 +92,7 @@ require (
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zitadel/logging v0.6.1 // indirect
github.com/zitadel/schema v1.3.0 // indirect
go.etcd.io/bbolt v1.3.11 // indirect
Expand Down
Loading

0 comments on commit e944359

Please sign in to comment.