Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add new go build tag no_openziti to reduce build size #795

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions bootstrap/handlers/auth_func.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright 2024 IOTech Ltd
*
* 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
*
* http://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 handlers

import (
"os"
"strconv"

"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"

"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/secret"

"github.com/labstack/echo/v4"
)

// NilAuthenticationHandlerFunc just invokes a nested handler
func NilAuthenticationHandlerFunc() echo.MiddlewareFunc {
return func(inner echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return inner(c)
}
}
}

// AutoConfigAuthenticationFunc auto-selects between a HandlerFunc
// wrapper that does authentication and a HandlerFunc wrapper that does not.
// By default, JWT validation is enabled in secure mode
// (i.e. when using a real secrets provider instead of a no-op stub)
//
// Set EDGEX_DISABLE_JWT_VALIDATION to 1, t, T, TRUE, true, or True
// to disable JWT validation. This might be wanted for an EdgeX
// adopter that wanted to only validate JWT's at the proxy layer,
// or as an escape hatch for a caller that cannot authenticate.
func AutoConfigAuthenticationFunc(secretProvider interfaces.SecretProviderExt, lc logger.LoggingClient) echo.MiddlewareFunc {
// Golang standard library treats an error as false
disableJWTValidation, _ := strconv.ParseBool(os.Getenv("EDGEX_DISABLE_JWT_VALIDATION"))
authenticationHook := NilAuthenticationHandlerFunc()
if secret.IsSecurityEnabled() && !disableJWTValidation {
authenticationHook = SecretStoreAuthenticationHandlerFunc(secretProvider, lc)
}
return authenticationHook
}
49 changes: 11 additions & 38 deletions bootstrap/handlers/auth_middleware.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build !no_openziti

/*******************************************************************************
* Copyright 2023 Intel Corporation
* Copyright 2023 IOTech Ltd
* Copyright 2023-2024 IOTech Ltd
*
* 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
Expand All @@ -18,14 +20,11 @@ package handlers
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"

"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"

"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/secret"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/zerotrust"
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"

"github.com/labstack/echo/v4"
"github.com/openziti/sdk-golang/ziti/edge"
Expand Down Expand Up @@ -57,11 +56,13 @@ func SecretStoreAuthenticationHandlerFunc(secretProvider interfaces.SecretProvid
lc.Debugf("Authorizing incoming call to '%s' via JWT (Authorization len=%d), %v", r.URL.Path, len(authHeader), secretProvider.IsZeroTrustEnabled())

if secretProvider.IsZeroTrustEnabled() {
zitiCtx := r.Context().Value(OpenZitiIdentityKey{})
zitiCtx := r.Context().Value(zerotrust.OpenZitiIdentityKey{})
if zitiCtx != nil {
zitiEdgeConn := zitiCtx.(edge.Conn)
lc.Debugf("Authorizing incoming connection via OpenZiti for %s", zitiEdgeConn.SourceIdentifier())
return inner(c)
if zitiEdgeConn, ok := zitiCtx.(edge.Conn); ok {
lc.Debugf("Authorizing incoming connection via OpenZiti for %s", zitiEdgeConn.SourceIdentifier())
return inner(c)
}
lc.Warn("context value for OpenZitiIdentityKey is not an edge.Conn")
}
lc.Debug("zero trust was enabled, but no marker was found. this is unexpected. falling back to token-based auth")
}
Expand Down Expand Up @@ -92,31 +93,3 @@ func SecretStoreAuthenticationHandlerFunc(secretProvider interfaces.SecretProvid
}
}
}

// NilAuthenticationHandlerFunc just invokes a nested handler
func NilAuthenticationHandlerFunc() echo.MiddlewareFunc {
return func(inner echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return inner(c)
}
}
}

// AutoConfigAuthenticationFunc auto-selects between a HandlerFunc
// wrapper that does authentication and a HandlerFunc wrapper that does not.
// By default, JWT validation is enabled in secure mode
// (i.e. when using a real secrets provider instead of a no-op stub)
//
// Set EDGEX_DISABLE_JWT_VALIDATION to 1, t, T, TRUE, true, or True
// to disable JWT validation. This might be wanted for an EdgeX
// adopter that wanted to only validate JWT's at the proxy layer,
// or as an escape hatch for a caller that cannot authenticate.
func AutoConfigAuthenticationFunc(secretProvider interfaces.SecretProviderExt, lc logger.LoggingClient) echo.MiddlewareFunc {
// Golang standard library treats an error as false
disableJWTValidation, _ := strconv.ParseBool(os.Getenv("EDGEX_DISABLE_JWT_VALIDATION"))
authenticationHook := NilAuthenticationHandlerFunc()
if secret.IsSecurityEnabled() && !disableJWTValidation {
authenticationHook = SecretStoreAuthenticationHandlerFunc(secretProvider, lc)
}
return authenticationHook
}
86 changes: 86 additions & 0 deletions bootstrap/handlers/auth_middleware_no_ziti.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//go:build no_openziti

/*******************************************************************************
* Copyright 2024 IOTech Ltd
*
* 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
*
* http://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 handlers

import (
"fmt"
"net/http"
"strings"

"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"

"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/interfaces"
"github.com/labstack/echo/v4"
)

// SecretStoreAuthenticationHandlerFunc prefixes an existing HandlerFunc
// with a OpenBao-based JWT authentication check. Usage:
//
// authenticationHook := handlers.NilAuthenticationHandlerFunc()
// if secret.IsSecurityEnabled() {
// lc := container.LoggingClientFrom(dic.Get)
// secretProvider := container.SecretProviderFrom(dic.Get)
// authenticationHook = handlers.SecretStoreAuthenticationHandlerFunc(secretProvider, lc)
// }
// For optionally-authenticated requests
// r.HandleFunc("path", authenticationHook(handlerFunc)).Methods(http.MethodGet)
//
// For unauthenticated requests
// r.HandleFunc("path", handlerFunc).Methods(http.MethodGet)
//
// For typical usage, it is preferred to use AutoConfigAuthenticationFunc which
// will automatically select between a real and a fake JWT validation handler.
func SecretStoreAuthenticationHandlerFunc(secretProvider interfaces.SecretProviderExt, lc logger.LoggingClient) echo.MiddlewareFunc {
return func(inner echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
r := c.Request()
w := c.Response()
authHeader := r.Header.Get("Authorization")
lc.Debugf("Authorizing incoming call to '%s' via JWT (Authorization len=%d), %v", r.URL.Path, len(authHeader), secretProvider.IsZeroTrustEnabled())

if secretProvider.IsZeroTrustEnabled() {
// this implementation will be pick up in the build when build tag no_openziti is specified, where
// OpenZiti packages are not included and the Zero Trust feature is not available.
lc.Info("zero trust was enabled, but service is built with no_openziti flag. falling back to token-based auth")
}

authParts := strings.Split(authHeader, " ")
if len(authParts) >= 2 && strings.EqualFold(authParts[0], "Bearer") {
token := authParts[1]
validToken, err := secretProvider.IsJWTValid(token)
if err != nil {
lc.Errorf("Error checking JWT validity: %v", err)
// set Response.Committed to true in order to rewrite the status code
w.Committed = false
return echo.NewHTTPError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
} else if !validToken {
lc.Warnf("Request to '%s' UNAUTHORIZED", r.URL.Path)
// set Response.Committed to true in order to rewrite the status code
w.Committed = false
return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}
lc.Debugf("Request to '%s' authorized", r.URL.Path)
return inner(c)
}
err := fmt.Errorf("unable to parse JWT for call to '%s'; unauthorized", r.URL.Path)
lc.Errorf("%v", err)
// set Response.Committed to true in order to rewrite the status code
w.Committed = false
return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}
}
}
98 changes: 3 additions & 95 deletions bootstrap/handlers/httpserver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*******************************************************************************
* Copyright 2019 Dell Inc.
* Copyright 2021-2023 IOTech Ltd
* Copyright 2021-2024 IOTech Ltd
* Copyright 2023 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
Expand All @@ -19,31 +19,24 @@ package handlers
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"

"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
commonDTO "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common"

"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/config"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/startup"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/zerotrust"
"github.com/edgexfoundry/go-mod-bootstrap/v4/di"

"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/zerotrust"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
edge_apis "github.com/openziti/sdk-golang/edge-apis"
"github.com/openziti/sdk-golang/ziti"
"github.com/openziti/sdk-golang/ziti/edge"
)

// HttpServer contains references to dependencies required by the http server implementation.
Expand All @@ -54,11 +47,6 @@ type HttpServer struct {
serverKey string
}

type ZitiContext struct {
c *ziti.Context
}
type OpenZitiIdentityKey struct{}

// NewHttpServer is a factory method that returns an initialized HttpServer receiver struct.
func NewHttpServer(router *echo.Echo, doListenAndServe bool, serviceKey string) *HttpServer {
return &HttpServer{
Expand Down Expand Up @@ -139,8 +127,6 @@ func (b *HttpServer) BootstrapHandler(
Timeout: timeout,
}))

zc := &ZitiContext{}

b.router.Use(RequestLimitMiddleware(bootstrapConfig.Service.MaxRequestSize, lc))

b.router.Use(ProcessCORS(bootstrapConfig.Service.CORSConfiguration))
Expand All @@ -153,7 +139,6 @@ func (b *HttpServer) BootstrapHandler(
Handler: b.router,
ReadHeaderTimeout: 5 * time.Second, // G112: A configured ReadHeaderTimeout in the http.Server averts a potential Slowloris Attack
}
server.ConnContext = mutator

wg.Add(1)
go func() {
Expand All @@ -174,76 +159,7 @@ func (b *HttpServer) BootstrapHandler(
}()

b.isRunning = true
listenMode := strings.ToLower(bootstrapConfig.Service.SecurityOptions[config.SecurityModeKey])
switch listenMode {
case zerotrust.ZeroTrustMode:
secretProvider := container.SecretProviderExtFrom(dic.Get)
if secretProvider == nil {
err = errors.New("secret provider is nil. cannot proceed with zero trust configuration")
break
}
secretProvider.EnableZeroTrust() //mark the secret provider as zero trust enabled
var zitiCtx ziti.Context
var ctxErr error
jwt, jwtErr := secretProvider.GetSelfJWT()
if jwtErr != nil {
lc.Errorf("could not load jwt: %v", jwtErr)
err = jwtErr
break
}
ozUrl := bootstrapConfig.Service.SecurityOptions["OpenZitiController"]
if !strings.Contains(ozUrl, "://") {
ozUrl = "https://" + ozUrl
}
caPool, caErr := ziti.GetControllerWellKnownCaPool(ozUrl)
if caErr != nil {
err = caErr
break
}

credentials := edge_apis.NewJwtCredentials(jwt)
credentials.CaPool = caPool

cfg := &ziti.Config{
ZtAPI: ozUrl + "/edge/client/v1",
Credentials: credentials,
}
cfg.ConfigTypes = append(cfg.ConfigTypes, "all")

zitiCtx, ctxErr = ziti.NewContext(cfg)
if ctxErr != nil {
err = ctxErr
break
}

ozServiceName := zerotrust.OpenZitiServicePrefix + b.serverKey
lc.Infof("Using OpenZiti service name: %s", ozServiceName)
for t.HasNotElapsed() {
ln, listenErr := zitiCtx.Listen(ozServiceName)
if listenErr != nil {
err = fmt.Errorf("could not bind service %s: %s", ozServiceName, listenErr.Error())
t.SleepForInterval()
} else {
zc.c = &zitiCtx
lc.Infof("listening on overlay network. ListenMode '%s' at %s", listenMode, addr)
err = server.Serve(ln)
break
}
}
if !t.HasNotElapsed() {
lc.Error("could not listen on the OpenZiti overlay network. timeout reached")
}
case "http":
fallthrough
default:
lc.Infof("listening on underlay network. ListenMode '%s' at %s", listenMode, addr)
ln, listenErr := net.Listen("tcp", addr)
if listenErr != nil {
err = listenErr
break
}
err = server.Serve(ln)
}
err = zerotrust.ListenOnMode(bootstrapConfig, b.serverKey, addr, t, server, dic)

// "Server closed" error occurs when Shutdown above is called in the Done processing, so it can be ignored
if err != nil && err != http.ErrServerClosed {
Expand Down Expand Up @@ -292,11 +208,3 @@ func RequestLimitMiddleware(sizeLimit int64, lc logger.LoggingClient) echo.Middl
}
}
}

func mutator(srcCtx context.Context, c net.Conn) context.Context {
if zitiConn, ok := c.(edge.Conn); ok {
return context.WithValue(srcCtx, OpenZitiIdentityKey{}, zitiConn)
}

return srcCtx
}
Loading