mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 08:45:41 +00:00
Ensure controller is initialized before raft. Fixes #2279
This commit is contained in:
@@ -37,7 +37,7 @@ func NewRequestContext(rw http.ResponseWriter, r *http.Request) api.RequestConte
|
||||
Request: r,
|
||||
}
|
||||
|
||||
requestContext.Responder = api.NewResponder(requestContext, fabricResponseMapper{})
|
||||
requestContext.Responder = api.NewResponder(requestContext, FabricResponseMapper{})
|
||||
|
||||
return requestContext
|
||||
}
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
Copyright NetFoundry Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api_impl
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/channel/v2"
|
||||
"github.com/openziti/channel/v2/websockets"
|
||||
"github.com/openziti/foundation/v2/concurrenz"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/ziti/controller/handler_mgmt"
|
||||
"github.com/openziti/ziti/controller/network"
|
||||
"github.com/openziti/ziti/controller/rest_client"
|
||||
"github.com/openziti/ziti/controller/rest_server"
|
||||
"github.com/openziti/ziti/controller/rest_server/operations"
|
||||
"github.com/openziti/ziti/controller/xmgmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ServerHeader = "server"
|
||||
)
|
||||
|
||||
var _ xweb.ApiHandlerFactory = &ManagementApiFactory{}
|
||||
|
||||
type ManagementApiFactory struct {
|
||||
InitFunc func(managementApi *ManagementApiHandler) error
|
||||
network *network.Network
|
||||
nodeId identity.Identity
|
||||
xmgmts *concurrenz.CopyOnWriteSlice[xmgmt.Xmgmt]
|
||||
}
|
||||
|
||||
func (factory *ManagementApiFactory) Validate(_ *xweb.InstanceConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewManagementApiFactory(nodeId identity.Identity, network *network.Network, xmgmts *concurrenz.CopyOnWriteSlice[xmgmt.Xmgmt]) *ManagementApiFactory {
|
||||
pfxlog.Logger().Infof("initializing management api factory with %d xmgmt instances", len(xmgmts.Value()))
|
||||
return &ManagementApiFactory{
|
||||
network: network,
|
||||
nodeId: nodeId,
|
||||
xmgmts: xmgmts,
|
||||
}
|
||||
}
|
||||
|
||||
func (factory *ManagementApiFactory) Binding() string {
|
||||
return FabricApiBinding
|
||||
}
|
||||
|
||||
func (factory *ManagementApiFactory) New(_ *xweb.ServerConfig, options map[interface{}]interface{}) (xweb.ApiHandler, error) {
|
||||
managementSpec, err := loads.Embedded(rest_server.SwaggerJSON, rest_server.FlatSwaggerJSON)
|
||||
if err != nil {
|
||||
pfxlog.Logger().Fatalln(err)
|
||||
}
|
||||
|
||||
fabricAPI := operations.NewZitiFabricAPI(managementSpec)
|
||||
fabricAPI.ServeError = ServeError
|
||||
|
||||
if requestWrapper == nil {
|
||||
requestWrapper = &FabricRequestWrapper{
|
||||
nodeId: factory.nodeId,
|
||||
network: factory.network,
|
||||
}
|
||||
}
|
||||
|
||||
for _, router := range routers {
|
||||
router.Register(fabricAPI, requestWrapper)
|
||||
}
|
||||
|
||||
managementApiHandler, err := NewManagementApiHandler(fabricAPI, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
managementApiHandler.bindHandler = handler_mgmt.NewBindHandler(factory.network, factory.xmgmts)
|
||||
|
||||
if factory.InitFunc != nil {
|
||||
if err := factory.InitFunc(managementApiHandler); err != nil {
|
||||
return nil, fmt.Errorf("error running on init func: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return managementApiHandler, nil
|
||||
}
|
||||
|
||||
func NewManagementApiHandler(fabricApi *operations.ZitiFabricAPI, options map[interface{}]interface{}) (*ManagementApiHandler, error) {
|
||||
managementApi := &ManagementApiHandler{
|
||||
fabricApi: fabricApi,
|
||||
options: options,
|
||||
}
|
||||
|
||||
managementApi.handler = managementApi.newHandler()
|
||||
managementApi.wsHandler = requestWrapper.WrapWsHandler(http.HandlerFunc(managementApi.handleWebSocket))
|
||||
managementApi.wsUrl = rest_client.DefaultBasePath + "/ws-api"
|
||||
|
||||
return managementApi, nil
|
||||
}
|
||||
|
||||
type ManagementApiHandler struct {
|
||||
fabricApi *operations.ZitiFabricAPI
|
||||
handler http.Handler
|
||||
wsHandler http.Handler
|
||||
wsUrl string
|
||||
options map[interface{}]interface{}
|
||||
bindHandler channel.BindHandler
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) Binding() string {
|
||||
return FabricApiBinding
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) Options() map[interface{}]interface{} {
|
||||
return managementApi.options
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) RootPath() string {
|
||||
return rest_client.DefaultBasePath
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) IsHandler(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, managementApi.RootPath())
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path == managementApi.wsUrl {
|
||||
managementApi.wsHandler.ServeHTTP(writer, request)
|
||||
} else {
|
||||
managementApi.handler.ServeHTTP(writer, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) newHandler() http.Handler {
|
||||
innerManagementHandler := managementApi.fabricApi.Serve(nil)
|
||||
return requestWrapper.WrapHttpHandler(innerManagementHandler)
|
||||
}
|
||||
|
||||
func (managementApi *ManagementApiHandler) handleWebSocket(writer http.ResponseWriter, request *http.Request) {
|
||||
log := pfxlog.Logger()
|
||||
log.Debug("handling mgmt channel websocket upgrade")
|
||||
upgrader := websocket.Upgrader{}
|
||||
conn, err := upgrader.Upgrade(writer, request, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("unable to upgrade request to websocket")
|
||||
return
|
||||
}
|
||||
|
||||
var certs []*x509.Certificate
|
||||
if request.TLS != nil {
|
||||
certs = request.TLS.PeerCertificates
|
||||
}
|
||||
|
||||
id := &identity.TokenId{Token: "mgmt"}
|
||||
underlayFactory := websockets.NewUnderlayFactory(id, conn, certs)
|
||||
|
||||
_, err = channel.NewChannel("mgmt", underlayFactory, managementApi.bindHandler, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("unable to create channel over websocket")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
Copyright NetFoundry Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api_impl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/xweb/v2"
|
||||
"github.com/openziti/ziti/controller/network"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ xweb.ApiHandlerFactory = &MetricsApiFactory{}
|
||||
|
||||
type MetricsApiFactory struct {
|
||||
network *network.Network
|
||||
nodeId identity.Identity
|
||||
}
|
||||
|
||||
func (factory *MetricsApiFactory) Validate(_ *xweb.InstanceConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMetricsApiFactory(nodeId identity.Identity, network *network.Network) *MetricsApiFactory {
|
||||
return &MetricsApiFactory{
|
||||
network: network,
|
||||
nodeId: nodeId,
|
||||
}
|
||||
}
|
||||
|
||||
func (factory *MetricsApiFactory) Binding() string {
|
||||
return MetricApiBinding
|
||||
}
|
||||
|
||||
func (factory *MetricsApiFactory) New(_ *xweb.ServerConfig, options map[interface{}]interface{}) (xweb.ApiHandler, error) {
|
||||
|
||||
metricsApiHandler, err := NewMetricsApiHandler(factory.network, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metricsApiHandler, nil
|
||||
}
|
||||
|
||||
func NewMetricsApiHandler(n *network.Network, options map[interface{}]interface{}) (*MetricsApiHandler, error) {
|
||||
metricsApi := &MetricsApiHandler{
|
||||
options: options,
|
||||
network: n,
|
||||
inspectMgr: network.NewInspectionsManager(n),
|
||||
}
|
||||
|
||||
if value, found := options["scrapeCert"]; found {
|
||||
if f, ok := value.(string); ok {
|
||||
p, err := os.ReadFile(f)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(p)
|
||||
if block == nil {
|
||||
err := errors.New("failed to decode metrics api scrapeCert")
|
||||
return nil, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
err := errors.New("failed to parse certificate: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
metricsApi.scrapeCert = cert
|
||||
} else {
|
||||
return nil, errors.New("invalid configuration found for metrics pem. The scrapeCert must be a string")
|
||||
}
|
||||
} else {
|
||||
pfxlog.Logger().Info("Metrics are enabled on /metrics, but no scrapeCert is provided in the controller configuration. Metrics are exposed without any authorization.")
|
||||
}
|
||||
|
||||
includeTimestamps := false
|
||||
if value, found := options["includeTimestamps"]; found {
|
||||
if t, ok := value.(bool); ok {
|
||||
includeTimestamps = t
|
||||
pfxlog.Logger().Debugf("includeTimestamps set to %v in Prometheus metrics exporter", t)
|
||||
}
|
||||
}
|
||||
|
||||
metricsApi.modelMapper = NewMetricsModelMapper(n, "prometheus", includeTimestamps)
|
||||
metricsApi.handler = metricsApi.newHandler()
|
||||
|
||||
return metricsApi, nil
|
||||
}
|
||||
|
||||
type MetricsApiHandler struct {
|
||||
inspectMgr *network.InspectionsManager
|
||||
handler http.Handler
|
||||
network *network.Network
|
||||
scrapeCert *x509.Certificate
|
||||
modelMapper MetricsModelMapper
|
||||
options map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) Binding() string {
|
||||
return MetricApiBinding
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) Options() map[interface{}]interface{} {
|
||||
return metricsApi.options
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) RootPath() string {
|
||||
return "/metrics"
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) IsHandler(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, metricsApi.RootPath())
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
metricsApi.handler.ServeHTTP(writer, request)
|
||||
}
|
||||
|
||||
func (metricsApi *MetricsApiHandler) newHandler() http.Handler {
|
||||
handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if nil != metricsApi.scrapeCert {
|
||||
certOk := false
|
||||
for _, r := range r.TLS.PeerCertificates {
|
||||
if bytes.Equal(metricsApi.scrapeCert.Signature, r.Signature) {
|
||||
certOk = true
|
||||
}
|
||||
}
|
||||
|
||||
if !certOk {
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
inspection := metricsApi.inspectMgr.Inspect(".*", []string{"metrics:prometheus"})
|
||||
|
||||
metricsResult, err := metricsApi.modelMapper.MapInspectResultToMetricsResult(inspection)
|
||||
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("Failed to convert metrics to prometheus format %s:%s", metricsApi.network.GetAppId(), err.Error())))
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
} else {
|
||||
if _, err = rw.Write([]byte(*metricsResult)); err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return handler
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package api_impl
|
||||
|
||||
import (
|
||||
"github.com/openziti/foundation/v2/errorz"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/rest_model"
|
||||
"github.com/openziti/foundation/v2/errorz"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -28,16 +28,16 @@ func RespondWithOk(responder api.Responder, data interface{}, meta *rest_model.M
|
||||
}, http.StatusOK)
|
||||
}
|
||||
|
||||
type fabricResponseMapper struct{}
|
||||
type FabricResponseMapper struct{}
|
||||
|
||||
func (self fabricResponseMapper) EmptyOkData() interface{} {
|
||||
func (self FabricResponseMapper) EmptyOkData() interface{} {
|
||||
return &rest_model.Empty{
|
||||
Data: map[string]interface{}{},
|
||||
Meta: &rest_model.Meta{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self fabricResponseMapper) MapApiError(requestId string, apiError *errorz.ApiError) interface{} {
|
||||
func (self FabricResponseMapper) MapApiError(requestId string, apiError *errorz.ApiError) interface{} {
|
||||
return &rest_model.APIErrorEnvelope{
|
||||
Error: ToRestModel(apiError, requestId),
|
||||
Meta: &rest_model.Meta{
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package api_impl
|
||||
|
||||
import "github.com/openziti/ziti/controller/rest_server/operations"
|
||||
import (
|
||||
"github.com/openziti/ziti/controller/rest_server/operations"
|
||||
)
|
||||
|
||||
var routers []Router
|
||||
var Routers []Router
|
||||
|
||||
func AddRouter(router Router) {
|
||||
routers = append(routers, router)
|
||||
Routers = append(Routers, router)
|
||||
}
|
||||
|
||||
type Router interface {
|
||||
|
||||
@@ -1,134 +1,16 @@
|
||||
package api_impl
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"github.com/go-openapi/runtime"
|
||||
openApiMiddleware "github.com/go-openapi/runtime/middleware"
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/ziti/common/build"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/ziti/controller/api"
|
||||
"github.com/openziti/ziti/controller/apierror"
|
||||
"github.com/openziti/ziti/controller/network"
|
||||
"github.com/openziti/ziti/controller/rest_server"
|
||||
"github.com/openziti/foundation/v2/errorz"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var requestWrapper RequestWrapper
|
||||
|
||||
func OverrideRequestWrapper(rw RequestWrapper) {
|
||||
if requestWrapper != nil {
|
||||
pfxlog.Logger().Warn("requestWrapper overridden more than once")
|
||||
}
|
||||
requestWrapper = rw
|
||||
}
|
||||
|
||||
type RequestHandler func(network *network.Network, rc api.RequestContext)
|
||||
|
||||
type RequestWrapper interface {
|
||||
WrapRequest(handler RequestHandler, request *http.Request, entityId, entitySubId string) openApiMiddleware.Responder
|
||||
WrapRequest(handler RequestHandler, request *http.Request, entityId, entitySubId string) middleware.Responder
|
||||
WrapHttpHandler(handler http.Handler) http.Handler
|
||||
WrapWsHandler(handler http.Handler) http.Handler
|
||||
}
|
||||
|
||||
type FabricRequestWrapper struct {
|
||||
nodeId identity.Identity
|
||||
network *network.Network
|
||||
}
|
||||
|
||||
func (self *FabricRequestWrapper) WrapRequest(handler RequestHandler, request *http.Request, entityId, entitySubId string) openApiMiddleware.Responder {
|
||||
return openApiMiddleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
||||
rc, err := api.GetRequestContextFromHttpContext(request)
|
||||
|
||||
if rc == nil {
|
||||
rc = NewRequestContext(writer, request)
|
||||
}
|
||||
|
||||
rc.SetProducer(producer)
|
||||
rc.SetEntityId(entityId)
|
||||
rc.SetEntitySubId(entitySubId)
|
||||
|
||||
if err != nil {
|
||||
pfxlog.Logger().WithError(err).Error("could not retrieve request context")
|
||||
rc.RespondWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
handler(self.network, rc)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FabricRequestWrapper) WrapHttpHandler(handler http.Handler) http.Handler {
|
||||
wrapper := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == FabricRestApiSpecUrl {
|
||||
rw.Header().Set("content-type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write(rest_server.SwaggerJSON)
|
||||
return
|
||||
}
|
||||
|
||||
rc := NewRequestContext(rw, r)
|
||||
|
||||
if err := self.verifyCert(r); err != nil {
|
||||
rc.RespondWithError(apierror.NewInvalidAuth())
|
||||
return
|
||||
}
|
||||
|
||||
api.AddRequestContextToHttpContext(r, rc)
|
||||
|
||||
//after request context is filled so that api session is present for session expiration headers
|
||||
buildInfo := build.GetBuildInfo()
|
||||
if buildInfo != nil {
|
||||
rc.GetResponseWriter().Header().Set(ServerHeader, "ziti-controller/"+buildInfo.Version())
|
||||
}
|
||||
|
||||
handler.ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
return api.TimeoutHandler(api.WrapCorsHandler(wrapper), 10*time.Second, apierror.NewTimeoutError(), fabricResponseMapper{})
|
||||
}
|
||||
|
||||
func (self *FabricRequestWrapper) WrapWsHandler(handler http.Handler) http.Handler {
|
||||
wrapper := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if err := self.verifyCert(r); err != nil {
|
||||
rc := NewRequestContext(rw, r)
|
||||
rc.RespondWithError(apierror.NewInvalidAuth())
|
||||
return
|
||||
}
|
||||
|
||||
handler.ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func (self *FabricRequestWrapper) verifyCert(r *http.Request) error {
|
||||
certificates := r.TLS.PeerCertificates
|
||||
if len(certificates) == 0 {
|
||||
return errors.New("no certificates provided, unable to verify dialer")
|
||||
}
|
||||
|
||||
config := self.nodeId.ServerTLSConfig()
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: config.RootCAs,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||
}
|
||||
|
||||
var errorList errorz.MultipleErrors
|
||||
|
||||
for _, cert := range certificates {
|
||||
if _, err := cert.Verify(opts); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
errorList = append(errorList, err)
|
||||
}
|
||||
}
|
||||
|
||||
//goland:noinspection GoNilness
|
||||
return errorList.ToError()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user