package main import ( "net/http" "github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/user" "github.com/azukaar/cosmos-server/src/configapi" "github.com/azukaar/cosmos-server/src/proxy" "github.com/azukaar/cosmos-server/src/docker" "github.com/azukaar/cosmos-server/src/authorizationserver" "github.com/azukaar/cosmos-server/src/market" "github.com/azukaar/cosmos-server/src/constellation" "github.com/azukaar/cosmos-server/src/metrics" "github.com/gorilla/mux" "strconv" "time" "os" "strings" "github.com/go-chi/chi/middleware" "github.com/go-chi/httprate" "crypto/tls" spa "github.com/roberthodgen/spa-server" "github.com/foomo/tlsconfig" "context" ) var serverPortHTTP = "" var serverPortHTTPS = "" var HTTPServer *http.Server var HTTPServer2 *http.Server func startHTTPServer(router *mux.Router) error { HTTPServer2 = nil HTTPServer = &http.Server{ Addr: "0.0.0.0:" + serverPortHTTP, ReadTimeout: 0, ReadHeaderTimeout: 10 * time.Second, WriteTimeout: 0, IdleTimeout: 30 * time.Second, Handler: router, DisableGeneralOptionsHandler: true, } docker.CheckPorts() utils.Log("Listening to HTTP on : 0.0.0.0:" + serverPortHTTP) return HTTPServer.ListenAndServe() } func startHTTPSServer(router *mux.Router) error { //config := utils.GetMainConfig() utils.IsHTTPS = true // redirect http to https go (func () { httpRouter := mux.NewRouter() httpRouter.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // change port in host if strings.HasSuffix(r.Host, ":" + serverPortHTTP) { if serverPortHTTPS != "443" { r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)] + ":" + serverPortHTTPS } else { r.Host = r.Host[:len(r.Host)-len(":" + serverPortHTTP)] } } http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently) }) HTTPServer2 = &http.Server{ Addr: "0.0.0.0:" + serverPortHTTP, ReadTimeout: 0, ReadHeaderTimeout: 10 * time.Second, WriteTimeout: 0, IdleTimeout: 30 * time.Second, Handler: httpRouter, DisableGeneralOptionsHandler: true, } err := HTTPServer2.ListenAndServe() if err != nil && err != http.ErrServerClosed { utils.Fatal("Listening to HTTP (Redirecting to HTTPS)", err) } })() utils.Log("Listening to HTTP on :" + serverPortHTTP) utils.Log("Listening to HTTPS on :" + serverPortHTTPS) config := utils.GetMainConfig() HTTPConfig := config.HTTPConfig var tlsCert = HTTPConfig.TLSCert var tlsKey= HTTPConfig.TLSKey tlsConf := tlsconfig.NewServerTLSConfig(tlsconfig.TLSModeServerStrict) cert, errCert := tls.X509KeyPair(([]byte)(tlsCert), ([]byte)(tlsKey)) if errCert != nil { config.HTTPConfig.ForceHTTPSCertificateRenewal = true utils.SetBaseMainConfig(config) utils.Fatal("Getting Certificate pair", errCert) } tlsConf.Certificates = []tls.Certificate{cert} HTTPServer = &http.Server{ TLSConfig: tlsConf, Addr: "0.0.0.0:" + serverPortHTTPS, ReadTimeout: 0, ReadHeaderTimeout: 10 * time.Second, WriteTimeout: 0, IdleTimeout: 30 * time.Second, Handler: router, DisableGeneralOptionsHandler: true, } // Redirect ports docker.CheckPorts() utils.Log("Now listening to HTTPS on :" + serverPortHTTPS) return HTTPServer.ListenAndServeTLS("", "") } func tokenMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //Header.Del r.Header.Del("x-cosmos-user") r.Header.Del("x-cosmos-role") r.Header.Del("x-cosmos-mfa") u, err := user.RefreshUserToken(w, r) if err != nil { return } r.Header.Set("x-cosmos-user", u.Nickname) r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role))) r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState))) next.ServeHTTP(w, r) }) } func SecureAPI(userRouter *mux.Router, public bool, publicCors bool) { if(!public) { userRouter.Use(tokenMiddleware) } userRouter.Use(proxy.SmartShieldMiddleware( "__COSMOS", utils.ProxyRouteConfig{ Name: "Cosmos-Internal", SmartShield: utils.SmartShieldPolicy{ Enabled: true, PolicyStrictness: 1, PerUserRequestLimit: 6000, }, }, )) if(publicCors || public) { userRouter.Use(utils.PublicCORS) } userRouter.Use(utils.MiddlewareTimeout(45 * time.Second)) userRouter.Use(httprate.Limit(180, 1*time.Minute, httprate.WithKeyFuncs(httprate.KeyByIP), httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { utils.Error("Too many requests. Throttling", nil) utils.HTTPError(w, "Too many requests", http.StatusTooManyRequests, "HTTP003") return }), )) } func CertificateIsExpiredSoon(validUntil time.Time) bool { // allow 5 days of leeway isValid := time.Now().Add(45 * 24 * time.Hour).Before(validUntil) if !isValid { utils.TriggerEvent( "cosmos.proxy.certificate", "Cosmos Certificate Expire Soon", "warning", "", map[string]interface{}{ }) utils.Log("Certificate is not valid anymore. Needs refresh") } return isValid } func CertificateIsExpired(validUntil time.Time) bool { // allow 5 days of leeway isValid := time.Now().Before(validUntil) if !isValid { utils.Log("Certificate is not valid anymore. Needs refresh") } return isValid } func InitServer() *mux.Router { utils.RestartHTTPServer = RestartServer baseMainConfig := utils.GetBaseMainConfig() config := utils.GetMainConfig() HTTPConfig := config.HTTPConfig serverPortHTTP = HTTPConfig.HTTPPort serverPortHTTPS = HTTPConfig.HTTPSPort var tlsCert = HTTPConfig.TLSCert var tlsKey= HTTPConfig.TLSKey domains := utils.GetAllHostnames(true, true) oldDomains := baseMainConfig.HTTPConfig.TLSKeyHostsCached falledBack := false NeedsRefresh := baseMainConfig.HTTPConfig.ForceHTTPSCertificateRenewal || (tlsCert == "" || tlsKey == "") || utils.HasAnyNewItem(domains, oldDomains) || !CertificateIsExpiredSoon(baseMainConfig.HTTPConfig.TLSValidUntil) // If we have a certificate, we can fallback to it if necessary CanFallback := tlsCert != "" && tlsKey != "" && len(config.HTTPConfig.TLSKeyHostsCached) > 0 && config.HTTPConfig.TLSKeyHostsCached[0] == config.HTTPConfig.Hostname && CertificateIsExpired(baseMainConfig.HTTPConfig.TLSValidUntil) if(NeedsRefresh && config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) { if(config.HTTPConfig.DNSChallengeProvider != "") { newEnv := config.HTTPConfig.DNSChallengeConfig for key, value := range newEnv { os.Setenv(key, value) } } // Get Certificates pub, priv := utils.DoLetsEncrypt() if(pub == "" || priv == "") { if(!CanFallback) { utils.Error("Getting TLS certificate. Fallback to SELFSIGNED certificates", nil) HTTPConfig.HTTPSCertificateMode = utils.HTTPSCertModeList["SELFSIGNED"] falledBack = true } else { utils.Error("Getting TLS certificate. Fallback to previous certificate", nil) } } else { baseMainConfig.HTTPConfig.TLSCert = pub baseMainConfig.HTTPConfig.TLSKey = priv baseMainConfig.HTTPConfig.TLSKeyHostsCached = domains baseMainConfig.HTTPConfig.TLSValidUntil = time.Now().AddDate(0, 0, 90) baseMainConfig.HTTPConfig.ForceHTTPSCertificateRenewal = false utils.SetBaseMainConfig(baseMainConfig) utils.Log("Saved new LETSENCRYPT TLS certificate") utils.TriggerEvent( "cosmos.proxy.certificate", "Cosmos Certificate Renewed", "important", "", map[string]interface{}{ "domains": domains, }) utils.WriteNotification(utils.Notification{ Recipient: "admin", Title: "Cosmos Certificate Renewed", Message: "The TLS certificate for the following domains has been renewed: " + strings.Join(domains, ", "), Level: "info", }) tlsCert = pub tlsKey = priv } } if(NeedsRefresh && HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"]) { utils.Log("Generating new TLS certificate for domains: " + strings.Join(domains, ", ")) pub, priv := utils.GenerateRSAWebCertificates(domains) if !falledBack { baseMainConfig.HTTPConfig.TLSCert = pub baseMainConfig.HTTPConfig.TLSKey = priv baseMainConfig.HTTPConfig.TLSKeyHostsCached = domains baseMainConfig.HTTPConfig.TLSValidUntil = time.Now().AddDate(0, 0, 364) baseMainConfig.HTTPConfig.ForceHTTPSCertificateRenewal = false utils.SetBaseMainConfig(baseMainConfig) utils.Log("Saved new SELFISGNED TLS certificate") utils.TriggerEvent( "cosmos.proxy.certificate", "Cosmos Certificate Renewed", "important", "", map[string]interface{}{ "domains": domains, }) utils.WriteNotification(utils.Notification{ Recipient: "admin", Title: "Cosmos Certificate Renewed", Message: "The TLS certificate for the following domains has been renewed: " + strings.Join(domains, ", "), Level: "info", }) } tlsCert = pub tlsKey = priv } if ((HTTPConfig.AuthPublicKey == "" || HTTPConfig.AuthPrivateKey == "") && HTTPConfig.GenerateMissingAuthCert) { utils.Log("Generating new Auth ED25519 certificate") pub, priv := utils.GenerateEd25519Certificates() baseMainConfig.HTTPConfig.AuthPublicKey = pub baseMainConfig.HTTPConfig.AuthPrivateKey = priv utils.SetBaseMainConfig(baseMainConfig) utils.Log("Saved new Auth ED25519 certificate") } utils.Log("Initialising HTTP(S) Router and all routes") router := mux.NewRouter().StrictSlash(true) router.Use(utils.BlockBannedIPs) router.Use(middleware.Logger) if config.BlockedCountries != nil && len(config.BlockedCountries) > 0 { router.Use(utils.BlockByCountryMiddleware(config.BlockedCountries, config.CountryBlacklistIsWhitelist)) } logoAPI := router.PathPrefix("/logo").Subrouter() SecureAPI(logoAPI, true, true) logoAPI.HandleFunc("/", SendLogo) srapi := router.PathPrefix("/cosmos").Subrouter() srapi.HandleFunc("/api/login", user.UserLogin) srapi.HandleFunc("/api/password-reset", user.ResetPassword) srapi.HandleFunc("/api/mfa", user.API2FA) srapi.HandleFunc("/api/dns", GetDNSRoute) srapi.HandleFunc("/api/dns-check", CheckDNSRoute) srapi.Use(utils.SetSecurityHeaders) srapi.HandleFunc("/api/status", StatusRoute) srapi.HandleFunc("/api/can-send-email", CanSendEmail) srapi.HandleFunc("/api/favicon", GetFavicon) srapi.HandleFunc("/api/ping", PingURL) srapi.HandleFunc("/api/newInstall", NewInstallRoute) srapi.HandleFunc("/api/logout", user.UserLogout) srapi.HandleFunc("/api/register", user.UserRegister) srapi.HandleFunc("/api/invite", user.UserResendInviteLink) srapi.HandleFunc("/api/me", user.Me) srapi.HandleFunc("/api/config", configapi.ConfigRoute) srapi.HandleFunc("/api/restart", configapi.ConfigApiRestart) srapi.HandleFunc("/api/users/{nickname}", user.UsersIdRoute) srapi.HandleFunc("/api/users", user.UsersRoute) srapi.HandleFunc("/api/images/pull-if-missing", docker.PullImageIfMissing) srapi.HandleFunc("/api/images/pull", docker.PullImage) srapi.HandleFunc("/api/images", docker.InspectImageRoute) srapi.HandleFunc("/api/volume/{volumeName}", docker.DeleteVolumeRoute) srapi.HandleFunc("/api/volumes", docker.VolumesRoute) srapi.HandleFunc("/api/network/{networkID}", docker.DeleteNetworkRoute) srapi.HandleFunc("/api/networks", docker.NetworkRoutes) srapi.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/auto-update/{status}", docker.AutoUpdateContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/logs", docker.GetContainerLogsRoute) srapi.HandleFunc("/api/servapps/{containerId}/terminal/{action}", docker.TerminalRoute) srapi.HandleFunc("/api/servapps/{containerId}/update", docker.UpdateContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/", docker.GetContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/network/{networkId}", docker.NetworkContainerRoutes) srapi.HandleFunc("/api/servapps/{containerId}/networks", docker.NetworkContainerRoutes) srapi.HandleFunc("/api/servapps/{containerId}/check-update", docker.CanUpdateImageRoute) srapi.HandleFunc("/api/servapps", docker.ContainersRoute) srapi.HandleFunc("/api/docker-service", docker.CreateServiceRoute) srapi.HandleFunc("/api/markets", market.MarketGet) srapi.HandleFunc("/api/upload/{name}", UploadImage) srapi.HandleFunc("/api/image/{name}", GetImage) srapi.HandleFunc("/api/get-backup", configapi.BackupFileApiGet) srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices) srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart) srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset) srapi.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting) srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig) srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs) srapi.HandleFunc("/api/constellation/block", constellation.DeviceBlock) srapi.HandleFunc("/api/events", metrics.API_ListEvents) srapi.HandleFunc("/api/metrics", metrics.API_GetMetrics) srapi.HandleFunc("/api/reset-metrics", metrics.API_ResetMetrics) srapi.HandleFunc("/api/list-metrics", metrics.ListMetrics) srapi.HandleFunc("/api/notifications/read", utils.MarkAsRead) srapi.HandleFunc("/api/notifications", utils.NotifGet) if(!config.HTTPConfig.AcceptAllInsecureHostname) { srapi.Use(utils.EnsureHostname) } srapi.Use(utils.EnsureHostnameCosmosAPI) SecureAPI(srapi, false, false) pwd,_ := os.Getwd() utils.Log("Starting in " + pwd) if _, err := os.Stat(pwd + "/static"); os.IsNotExist(err) { utils.Fatal("Static folder not found at " + pwd + "/static", err) } fs := spa.SpaHandler(pwd + "/static", "index.html") if(!config.HTTPConfig.AcceptAllInsecureHostname) { fs = utils.EnsureHostname(fs) } router.PathPrefix("/cosmos-ui").Handler(http.StripPrefix("/cosmos-ui", fs)) router = proxy.BuildFromConfig(router, HTTPConfig.ProxyConfig) router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/cosmos-ui", http.StatusTemporaryRedirect) })) userRouter := router.PathPrefix("/oauth2").Subrouter() SecureAPI(userRouter, false, true) serverRouter := router.PathPrefix("/oauth2").Subrouter() SecureAPI(serverRouter, true, true) wellKnownRouter := router.PathPrefix("/").Subrouter() SecureAPI(wellKnownRouter, true, true) authorizationserver.RegisterHandlers(wellKnownRouter, userRouter, serverRouter) return router } func StartServer() { router := InitServer() // start https server var errServ error for(errServ == http.ErrServerClosed || errServ == nil) { config := utils.GetMainConfig() HTTPConfig := config.HTTPConfig var tlsCert = HTTPConfig.TLSCert var tlsKey= HTTPConfig.TLSKey if ( ( HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"] || HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["PROVIDED"] || HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) && tlsCert != "" && tlsKey != "") { utils.Log("TLS certificate exist, starting HTTPS servers and redirecting HTTP to HTTPS") errServ = startHTTPSServer(router) } else { utils.Log("TLS certificates do not exists or are disabled, starting HTTP server only") errServ = startHTTPServer(router) } if errServ != nil && errServ != http.ErrServerClosed { utils.Fatal("Listening to HTTPS", errServ) } utils.Log("HTTPS Server closed. Restarting.") errServ = nil router = InitServer() } } func RestartServer() { utils.LetsEncryptErrors = []string{} IconCache = map[string]CachedImage{} utils.Log("Restarting HTTP Server...") LoadConfig() go func() { if HTTPServer2 != nil { HTTPServer2.Shutdown(context.Background()) } HTTPServer.Shutdown(context.Background()) }() utils.Log("HTTPServer shutdown.") }