Skip to content

Commit

Permalink
feat: Add new go build tag no_openziti to reduce build size (#795)
Browse files Browse the repository at this point in the history
* feat: Add new go build tag no_openziti to reduce build size

closes #789

The usage of OpenZiti packages to support zero trust feature significantly increases
the build size. For example, core-metadata increases from 14MB to 21MB, core-command
increases from 9.8MB to 17MB, device-virtual increases from 18MB to 31MB, and
app-service-configurable increases from 22Mb to 34MB. As many edge user scenarios
require to deploy EdgeX services on resource-constrained devices without security,
allowing that the services can be built without OpenZiti packages and ZeroTrust
features can be helpful to user cases which don't need zero trust feature.

This commit refactors the codes importing openziti packages with //go:build !no_openziti
directive and creates para codes that don't use openziti packages with
//go:build no_openziti directive, so users can simply build services by specifying
no_openziti tag.

Also remove vestigal zc variables and ZitiContext struct per discussion
with https://github.com/dovholuknf in
#659 (comment)

Signed-off-by: Jude Hung <[email protected]>

* feat: rename the zerotrust_no_ziti.go to no_ziti.go

Signed-off-by: Jude Hung <[email protected]>

---------

Signed-off-by: Jude Hung <[email protected]>
  • Loading branch information
judehung authored Nov 19, 2024
1 parent 45216ef commit 84aca22
Show file tree
Hide file tree
Showing 8 changed files with 505 additions and 240 deletions.
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

0 comments on commit 84aca22

Please sign in to comment.