package http import ( "context" "crypto/tls" "fmt" "log" "net/http" "path/filepath" "time" "github.com/portainer/libhelm" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/adminmonitor" "github.com/portainer/portainer/api/apikey" "github.com/portainer/portainer/api/crypto" "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/http/handler" "github.com/portainer/portainer/api/http/handler/auth" "github.com/portainer/portainer/api/http/handler/backup" "github.com/portainer/portainer/api/http/handler/customtemplates" "github.com/portainer/portainer/api/http/handler/edgegroups" "github.com/portainer/portainer/api/http/handler/edgejobs" "github.com/portainer/portainer/api/http/handler/edgestacks" "github.com/portainer/portainer/api/http/handler/edgetemplates" "github.com/portainer/portainer/api/http/handler/endpointedge" "github.com/portainer/portainer/api/http/handler/endpointgroups" "github.com/portainer/portainer/api/http/handler/endpointproxy" "github.com/portainer/portainer/api/http/handler/endpoints" "github.com/portainer/portainer/api/http/handler/file" "github.com/portainer/portainer/api/http/handler/helm" "github.com/portainer/portainer/api/http/handler/hostmanagement/openamt" kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes" "github.com/portainer/portainer/api/http/handler/ldap" "github.com/portainer/portainer/api/http/handler/motd" "github.com/portainer/portainer/api/http/handler/registries" "github.com/portainer/portainer/api/http/handler/resourcecontrols" "github.com/portainer/portainer/api/http/handler/roles" "github.com/portainer/portainer/api/http/handler/settings" sslhandler "github.com/portainer/portainer/api/http/handler/ssl" "github.com/portainer/portainer/api/http/handler/stacks" "github.com/portainer/portainer/api/http/handler/status" "github.com/portainer/portainer/api/http/handler/storybook" "github.com/portainer/portainer/api/http/handler/tags" "github.com/portainer/portainer/api/http/handler/teammemberships" "github.com/portainer/portainer/api/http/handler/teams" "github.com/portainer/portainer/api/http/handler/templates" "github.com/portainer/portainer/api/http/handler/upload" "github.com/portainer/portainer/api/http/handler/users" "github.com/portainer/portainer/api/http/handler/webhooks" "github.com/portainer/portainer/api/http/handler/websocket" "github.com/portainer/portainer/api/http/offlinegate" "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/proxy/factory/kubernetes" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/authorization" "github.com/portainer/portainer/api/internal/ssl" k8s "github.com/portainer/portainer/api/kubernetes" "github.com/portainer/portainer/api/kubernetes/cli" "github.com/portainer/portainer/api/scheduler" stackdeployer "github.com/portainer/portainer/api/stacks" ) // Server implements the portainer.Server interface type Server struct { AuthorizationService *authorization.Service BindAddress string BindAddressHTTPS string HTTPEnabled bool AssetsPath string Status *portainer.Status ReverseTunnelService portainer.ReverseTunnelService ComposeStackManager portainer.ComposeStackManager CryptoService portainer.CryptoService SignatureService portainer.DigitalSignatureService SnapshotService portainer.SnapshotService FileService portainer.FileService DataStore dataservices.DataStore GitService portainer.GitService OpenAMTService portainer.OpenAMTService APIKeyService apikey.APIKeyService JWTService dataservices.JWTService LDAPService portainer.LDAPService OAuthService portainer.OAuthService SwarmStackManager portainer.SwarmStackManager ProxyManager *proxy.Manager KubernetesTokenCacheManager *kubernetes.TokenCacheManager KubeConfigService k8s.KubeConfigService Handler *handler.Handler SSLService *ssl.Service DockerClientFactory *docker.ClientFactory KubernetesClientFactory *cli.ClientFactory KubernetesDeployer portainer.KubernetesDeployer HelmPackageManager libhelm.HelmPackageManager Scheduler *scheduler.Scheduler ShutdownCtx context.Context ShutdownTrigger context.CancelFunc StackDeployer stackdeployer.StackDeployer BaseURL string } // Start starts the HTTP server func (server *Server) Start() error { kubernetesTokenCacheManager := server.KubernetesTokenCacheManager requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, server.APIKeyService) rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour) offlineGate := offlinegate.NewOfflineGate() var authHandler = auth.NewHandler(requestBouncer, rateLimiter) authHandler.DataStore = server.DataStore authHandler.CryptoService = server.CryptoService authHandler.JWTService = server.JWTService authHandler.LDAPService = server.LDAPService authHandler.ProxyManager = server.ProxyManager authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager authHandler.OAuthService = server.OAuthService adminMonitor := adminmonitor.New(5*time.Minute, server.DataStore, server.ShutdownCtx) adminMonitor.Start() var backupHandler = backup.NewHandler(requestBouncer, server.DataStore, offlineGate, server.FileService.GetDatastorePath(), server.ShutdownTrigger, adminMonitor) var roleHandler = roles.NewHandler(requestBouncer) roleHandler.DataStore = server.DataStore var customTemplatesHandler = customtemplates.NewHandler(requestBouncer) customTemplatesHandler.DataStore = server.DataStore customTemplatesHandler.FileService = server.FileService customTemplatesHandler.GitService = server.GitService var edgeGroupsHandler = edgegroups.NewHandler(requestBouncer) edgeGroupsHandler.DataStore = server.DataStore var edgeJobsHandler = edgejobs.NewHandler(requestBouncer) edgeJobsHandler.DataStore = server.DataStore edgeJobsHandler.FileService = server.FileService edgeJobsHandler.ReverseTunnelService = server.ReverseTunnelService var edgeStacksHandler = edgestacks.NewHandler(requestBouncer) edgeStacksHandler.DataStore = server.DataStore edgeStacksHandler.FileService = server.FileService edgeStacksHandler.GitService = server.GitService edgeStacksHandler.KubernetesDeployer = server.KubernetesDeployer var edgeTemplatesHandler = edgetemplates.NewHandler(requestBouncer) edgeTemplatesHandler.DataStore = server.DataStore var endpointHandler = endpoints.NewHandler(requestBouncer) endpointHandler.DataStore = server.DataStore endpointHandler.FileService = server.FileService endpointHandler.ProxyManager = server.ProxyManager endpointHandler.SnapshotService = server.SnapshotService endpointHandler.K8sClientFactory = server.KubernetesClientFactory endpointHandler.ReverseTunnelService = server.ReverseTunnelService endpointHandler.ComposeStackManager = server.ComposeStackManager endpointHandler.AuthorizationService = server.AuthorizationService endpointHandler.BindAddress = server.BindAddress endpointHandler.BindAddressHTTPS = server.BindAddressHTTPS var endpointEdgeHandler = endpointedge.NewHandler(requestBouncer) endpointEdgeHandler.DataStore = server.DataStore endpointEdgeHandler.FileService = server.FileService endpointEdgeHandler.ReverseTunnelService = server.ReverseTunnelService var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer) endpointGroupHandler.AuthorizationService = server.AuthorizationService endpointGroupHandler.DataStore = server.DataStore var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer) endpointProxyHandler.DataStore = server.DataStore endpointProxyHandler.ProxyManager = server.ProxyManager endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService var kubernetesHandler = kubehandler.NewHandler(requestBouncer, server.AuthorizationService, server.DataStore, server.KubernetesClientFactory, server.BaseURL) kubernetesHandler.JwtService = server.JWTService var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public")) var endpointHelmHandler = helm.NewHandler(requestBouncer, server.DataStore, server.JWTService, server.KubernetesDeployer, server.HelmPackageManager, server.KubeConfigService) var helmTemplatesHandler = helm.NewTemplateHandler(requestBouncer, server.HelmPackageManager) var ldapHandler = ldap.NewHandler(requestBouncer) ldapHandler.DataStore = server.DataStore ldapHandler.FileService = server.FileService ldapHandler.LDAPService = server.LDAPService var motdHandler = motd.NewHandler(requestBouncer) var registryHandler = registries.NewHandler(requestBouncer) registryHandler.DataStore = server.DataStore registryHandler.FileService = server.FileService registryHandler.ProxyManager = server.ProxyManager registryHandler.K8sClientFactory = server.KubernetesClientFactory var resourceControlHandler = resourcecontrols.NewHandler(requestBouncer) resourceControlHandler.DataStore = server.DataStore var settingsHandler = settings.NewHandler(requestBouncer) settingsHandler.DataStore = server.DataStore settingsHandler.FileService = server.FileService settingsHandler.JWTService = server.JWTService settingsHandler.LDAPService = server.LDAPService settingsHandler.SnapshotService = server.SnapshotService var sslHandler = sslhandler.NewHandler(requestBouncer) sslHandler.SSLService = server.SSLService openAMTHandler, err := openamt.NewHandler(requestBouncer, server.DataStore) if err != nil { return err } if openAMTHandler != nil { openAMTHandler.OpenAMTService = server.OpenAMTService openAMTHandler.DataStore = server.DataStore } var stackHandler = stacks.NewHandler(requestBouncer) stackHandler.DataStore = server.DataStore stackHandler.DockerClientFactory = server.DockerClientFactory stackHandler.FileService = server.FileService stackHandler.KubernetesDeployer = server.KubernetesDeployer stackHandler.GitService = server.GitService stackHandler.Scheduler = server.Scheduler stackHandler.SwarmStackManager = server.SwarmStackManager stackHandler.ComposeStackManager = server.ComposeStackManager stackHandler.StackDeployer = server.StackDeployer var storybookHandler = storybook.NewHandler(server.AssetsPath) var tagHandler = tags.NewHandler(requestBouncer) tagHandler.DataStore = server.DataStore var teamHandler = teams.NewHandler(requestBouncer) teamHandler.DataStore = server.DataStore var teamMembershipHandler = teammemberships.NewHandler(requestBouncer) teamMembershipHandler.DataStore = server.DataStore var statusHandler = status.NewHandler(requestBouncer, server.Status) var templatesHandler = templates.NewHandler(requestBouncer) templatesHandler.DataStore = server.DataStore templatesHandler.FileService = server.FileService templatesHandler.GitService = server.GitService var uploadHandler = upload.NewHandler(requestBouncer) uploadHandler.FileService = server.FileService var userHandler = users.NewHandler(requestBouncer, rateLimiter, server.APIKeyService) userHandler.DataStore = server.DataStore userHandler.CryptoService = server.CryptoService var websocketHandler = websocket.NewHandler(server.KubernetesTokenCacheManager, requestBouncer) websocketHandler.DataStore = server.DataStore websocketHandler.SignatureService = server.SignatureService websocketHandler.ReverseTunnelService = server.ReverseTunnelService websocketHandler.KubernetesClientFactory = server.KubernetesClientFactory var webhookHandler = webhooks.NewHandler(requestBouncer) webhookHandler.DataStore = server.DataStore webhookHandler.DockerClientFactory = server.DockerClientFactory server.Handler = &handler.Handler{ RoleHandler: roleHandler, AuthHandler: authHandler, BackupHandler: backupHandler, CustomTemplatesHandler: customTemplatesHandler, EdgeGroupsHandler: edgeGroupsHandler, EdgeJobsHandler: edgeJobsHandler, EdgeStacksHandler: edgeStacksHandler, EdgeTemplatesHandler: edgeTemplatesHandler, EndpointGroupHandler: endpointGroupHandler, EndpointHandler: endpointHandler, EndpointHelmHandler: endpointHelmHandler, EndpointEdgeHandler: endpointEdgeHandler, EndpointProxyHandler: endpointProxyHandler, FileHandler: fileHandler, LDAPHandler: ldapHandler, HelmTemplatesHandler: helmTemplatesHandler, KubernetesHandler: kubernetesHandler, MOTDHandler: motdHandler, OpenAMTHandler: openAMTHandler, RegistryHandler: registryHandler, ResourceControlHandler: resourceControlHandler, SettingsHandler: settingsHandler, SSLHandler: sslHandler, StatusHandler: statusHandler, StackHandler: stackHandler, StorybookHandler: storybookHandler, TagHandler: tagHandler, TeamHandler: teamHandler, TeamMembershipHandler: teamMembershipHandler, TemplatesHandler: templatesHandler, UploadHandler: uploadHandler, UserHandler: userHandler, WebSocketHandler: websocketHandler, WebhookHandler: webhookHandler, } handler := offlineGate.WaitingMiddleware(time.Minute, server.Handler) if server.HTTPEnabled { go func() { log.Printf("[INFO] [http,server] [message: starting HTTP server on port %s]", server.BindAddress) httpServer := &http.Server{ Addr: server.BindAddress, Handler: handler, } go shutdown(server.ShutdownCtx, httpServer) err := httpServer.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Printf("[ERROR] [message: http server failed] [error: %s]", err) } }() } log.Printf("[INFO] [http,server] [message: starting HTTPS server on port %s]", server.BindAddressHTTPS) httpsServer := &http.Server{ Addr: server.BindAddressHTTPS, Handler: handler, } httpsServer.TLSConfig = crypto.CreateServerTLSConfiguration() httpsServer.TLSConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return server.SSLService.GetRawCertificate(), nil } go shutdown(server.ShutdownCtx, httpsServer) return httpsServer.ListenAndServeTLS("", "") } func shutdown(shutdownCtx context.Context, httpServer *http.Server) { <-shutdownCtx.Done() log.Println("[DEBUG] [http,server] [message: shutting down http server]") shutdownTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() err := httpServer.Shutdown(shutdownTimeout) if err != nil { fmt.Printf("[ERROR] [http,server] [message: failed shutdown http server] [error: %s]", err) } }