mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(server): use https by default (#5315) [EE-332]
This commit is contained in:
parent
3257cb1e28
commit
11d555bbd6
32 changed files with 1160 additions and 319 deletions
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
"github.com/portainer/portainer/api/http/handler/roles"
|
||||
"github.com/portainer/portainer/api/http/handler/settings"
|
||||
"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/tags"
|
||||
|
@ -54,6 +55,7 @@ type Handler struct {
|
|||
ResourceControlHandler *resourcecontrols.Handler
|
||||
RoleHandler *roles.Handler
|
||||
SettingsHandler *settings.Handler
|
||||
SSLHandler *ssl.Handler
|
||||
StackHandler *stacks.Handler
|
||||
StatusHandler *status.Handler
|
||||
TagHandler *tags.Handler
|
||||
|
@ -130,6 +132,8 @@ type Handler struct {
|
|||
// @tag.description Manage App Templates
|
||||
// @tag.name stacks
|
||||
// @tag.description Manage stacks
|
||||
// @tag.name ssl
|
||||
// @tag.description Manage ssl settings
|
||||
// @tag.name upload
|
||||
// @tag.description Upload files
|
||||
// @tag.name webhooks
|
||||
|
@ -199,6 +203,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api", h.UploadHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/users"):
|
||||
http.StripPrefix("/api", h.UserHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/ssl"):
|
||||
http.StripPrefix("/api", h.SSLHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/teams"):
|
||||
http.StripPrefix("/api", h.TeamHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/team_memberships"):
|
||||
|
|
29
api/http/handler/ssl/handler.go
Normal file
29
api/http/handler/ssl/handler.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package ssl
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/ssl"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle MOTD operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
SSLService *ssl.Service
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/ssl",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.sslInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/ssl",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.sslUpdate))).Methods(http.MethodPut)
|
||||
|
||||
return h
|
||||
}
|
29
api/http/handler/ssl/ssl_inspect.go
Normal file
29
api/http/handler/ssl/ssl_inspect.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package ssl
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// @id SSLInspect
|
||||
// @summary Inspect the ssl settings
|
||||
// @description Retrieve the ssl settings.
|
||||
// @description **Access policy**: administrator
|
||||
// @tags ssl
|
||||
// @security jwt
|
||||
// @produce json
|
||||
// @success 200 {object} portainer.SSLSettings "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied to access settings"
|
||||
// @failure 500 "Server error"
|
||||
// @router /ssl [get]
|
||||
func (handler *Handler) sslInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
settings, err := handler.SSLService.GetSSLSettings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Failed to fetch certificate info", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, settings)
|
||||
}
|
62
api/http/handler/ssl/ssl_update.go
Normal file
62
api/http/handler/ssl/ssl_update.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package ssl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
type sslUpdatePayload struct {
|
||||
Cert *string
|
||||
Key *string
|
||||
HTTPEnabled *bool
|
||||
}
|
||||
|
||||
func (payload *sslUpdatePayload) Validate(r *http.Request) error {
|
||||
if (payload.Cert == nil || payload.Key == nil) && payload.Cert != payload.Key {
|
||||
return errors.New("both certificate and key files should be provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id SSLUpdate
|
||||
// @summary Update the ssl settings
|
||||
// @description Update the ssl settings.
|
||||
// @description **Access policy**: administrator
|
||||
// @tags ssl
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param body body sslUpdatePayload true "SSL Settings"
|
||||
// @success 204 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 403 "Permission denied to access settings"
|
||||
// @failure 500 "Server error"
|
||||
// @router /ssl [put]
|
||||
func (handler *Handler) sslUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload sslUpdatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
if payload.Cert != nil {
|
||||
err = handler.SSLService.SetCertificates([]byte(*payload.Cert), []byte(*payload.Key))
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Failed to save certificate", err}
|
||||
}
|
||||
}
|
||||
|
||||
if payload.HTTPEnabled != nil {
|
||||
err = handler.SSLService.SetHTTPEnabled(*payload.HTTPEnabled)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Failed to force https", err}
|
||||
}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
}
|
|
@ -2,6 +2,7 @@ package http
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -31,6 +32,7 @@ import (
|
|||
"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/tags"
|
||||
|
@ -46,13 +48,16 @@ import (
|
|||
"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"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
)
|
||||
|
||||
// Server implements the portainer.Server interface
|
||||
type Server struct {
|
||||
AuthorizationService *authorization.Service
|
||||
AuthorizationService *authorization.Service
|
||||
BindAddress string
|
||||
BindAddressHTTPS string
|
||||
HTTPEnabled bool
|
||||
AssetsPath string
|
||||
Status *portainer.Status
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
|
@ -70,9 +75,7 @@ type Server struct {
|
|||
ProxyManager *proxy.Manager
|
||||
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||
Handler *handler.Handler
|
||||
SSL bool
|
||||
SSLCert string
|
||||
SSLKey string
|
||||
SSLService *ssl.Service
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
KubernetesClientFactory *cli.ClientFactory
|
||||
KubernetesDeployer portainer.KubernetesDeployer
|
||||
|
@ -175,6 +178,9 @@ func (server *Server) Start() error {
|
|||
settingsHandler.LDAPService = server.LDAPService
|
||||
settingsHandler.SnapshotService = server.SnapshotService
|
||||
|
||||
var sslHandler = sslhandler.NewHandler(requestBouncer)
|
||||
sslHandler.SSLService = server.SSLService
|
||||
|
||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||
stackHandler.DataStore = server.DataStore
|
||||
stackHandler.DockerClientFactory = server.DockerClientFactory
|
||||
|
@ -236,6 +242,7 @@ func (server *Server) Start() error {
|
|||
RegistryHandler: registryHandler,
|
||||
ResourceControlHandler: resourceControlHandler,
|
||||
SettingsHandler: settingsHandler,
|
||||
SSLHandler: sslHandler,
|
||||
StatusHandler: statusHandler,
|
||||
StackHandler: stackHandler,
|
||||
TagHandler: tagHandler,
|
||||
|
@ -248,31 +255,48 @@ func (server *Server) Start() error {
|
|||
WebhookHandler: webhookHandler,
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: server.BindAddress,
|
||||
Handler: server.Handler,
|
||||
}
|
||||
httpServer.Handler = offlineGate.WaitingMiddleware(time.Minute, httpServer.Handler)
|
||||
handler := offlineGate.WaitingMiddleware(time.Minute, server.Handler)
|
||||
|
||||
if server.SSL {
|
||||
httpServer.TLSConfig = crypto.CreateServerTLSConfiguration()
|
||||
return httpServer.ListenAndServeTLS(server.SSLCert, server.SSLKey)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go server.shutdown(httpServer)
|
||||
log.Printf("[INFO] [http,server] [message: starting HTTPS server on port %s]", server.BindAddressHTTPS)
|
||||
httpsServer := &http.Server{
|
||||
Addr: server.BindAddressHTTPS,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
return httpServer.ListenAndServe()
|
||||
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 (server *Server) shutdown(httpServer *http.Server) {
|
||||
<-server.ShutdownCtx.Done()
|
||||
func shutdown(shutdownCtx context.Context, httpServer *http.Server) {
|
||||
<-shutdownCtx.Done()
|
||||
|
||||
log.Println("[DEBUG] Shutting down http server")
|
||||
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("Failed shutdown http server: %s \n", err)
|
||||
fmt.Printf("[ERROR] [http,server] [message: failed shutdown http server] [error: %s]", err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue