* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package server
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
"time"
"github.com/emicklei/go-restful/v3"
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
"console-service/cmd/config"
"console-service/pkg/api/utility/v1beta1"
"console-service/pkg/auth"
"console-service/pkg/client/k8s"
"console-service/pkg/server/filters"
"console-service/pkg/server/request"
"console-service/pkg/server/runtime"
"console-service/pkg/zlog"
)
type CServer struct {
Server *http.Server
container *restful.Container
KubernetesClient k8s.Client
}
func NewServer(cfg *config.RunConfig, ctx context.Context) (*CServer, error) {
server := &CServer{}
httpServer, err := initServer(cfg)
if err != nil {
return nil, err
}
server.Server = httpServer
server.container = restful.NewContainer()
server.container.Router(restful.CurlyRouter{})
server.container.Filter(filters.RecordAccessLogs)
kubernetesClient, err := k8s.NewKubernetesClient(cfg.KubernetesCfg)
if err != nil {
return nil, err
}
server.KubernetesClient = kubernetesClient
return server, nil
}
func initServer(cfg *config.RunConfig) (*http.Server, error) {
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Server.InsecurePort),
ReadTimeout: 10 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 60 * time.Second,
ReadHeaderTimeout: 120 * time.Second,
}
if cfg.Server.SecurePort != 0 {
certificate, err := tls.LoadX509KeyPair(cfg.Server.CertFile, cfg.Server.PrivateKey)
if err != nil {
zlog.Errorf("error loading %s and %s , %v", cfg.Server.CertFile, cfg.Server.PrivateKey, err)
return nil, err
}
caCert, err := os.ReadFile(cfg.Server.CAFile)
if err != nil {
zlog.Errorf("error read %s, err: %v", cfg.Server.CAFile, err)
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
httpServer.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{certificate},
ClientAuth: tls.RequestClientCert,
MinVersion: tls.VersionTLS12,
ClientCAs: caCertPool,
}
httpServer.Addr = fmt.Sprintf(":%d", cfg.Server.SecurePort)
}
return httpServer, nil
}
func (s *CServer) Run(ctx context.Context) error {
var err error = nil
s.registerAPI()
s.Server.Handler = s.container
s.buildHandlerChain()
s.Server.Handler = addSecurityHeader(s.Server.Handler)
shutdownCtx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
<-ctx.Done()
err = s.Server.Shutdown(shutdownCtx)
}()
if s.Server.TLSConfig != nil {
err = s.Server.ListenAndServeTLS("", "")
} else {
err = s.Server.ListenAndServe()
}
return err
}
func (s *CServer) registerAPI() {
consoleWebService := runtime.GetConsoleWebService()
v1beta1.BindUtilityWebService(consoleWebService)
s.container.Add(consoleWebService)
urlruntime.Must(auth.AddToContainer(s.container, s.KubernetesClient.Config()))
}
func (s *CServer) buildHandlerChain() {
serverHandler := s.Server.Handler
staticPageHandler := filters.ProxyStatic(serverHandler, s.KubernetesClient.Config())
pluginHandler := filters.ProxyConsolePlugin(staticPageHandler, s.KubernetesClient.Config())
componentAPIHandler := filters.ProxyComponentRequest(serverHandler, pluginHandler, s.KubernetesClient)
kubeAPIHandler := filters.ProxyAPIServer(componentAPIHandler, s.KubernetesClient)
requestInfoResolver := &request.RequestInfoFactory{
RequestInfoFactory: &k8srequest.RequestInfoFactory{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
},
}
buildRequestInfoHandler := filters.BuildRequestInfo(kubeAPIHandler, requestInfoResolver, s.KubernetesClient.Config())
consoleAPIHandler := filters.HandleConsoleRequests(serverHandler, buildRequestInfoHandler)
authReqCheckHandler := filters.CheckRequestAuth(serverHandler, consoleAPIHandler, s.KubernetesClient.Config())
localeHandler := filters.WithLocale(authReqCheckHandler)
s.Server.Handler = localeHandler
}
func addSecurityHeader(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csp := "connect-src 'self' https:;frame-ancestors 'none';object-src 'none'"
w.Header().Set("Content-Security-Policy", csp)
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1")
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
next.ServeHTTP(w, r)
})
}