VYPR
High severityNVD Advisory· Published Nov 10, 2022· Updated Apr 23, 2025

Istio may allow identity impersonation if user has localhost access

CVE-2022-39388

Description

Istio is an open platform to connect, manage, and secure microservices. In versions on the 1.15.x branch prior to 1.15.3, a user can impersonate any workload identity within the service mesh if they have localhost access to the Istiod control plane. Version 1.15.3 contains a patch for this issue. There are no known workarounds.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/istio/istioGo
>= 1.15.0-beta.0, < 1.15.31.15.3

Affected products

1

Patches

2
9a643e270421

[release-1.15] fix localhost address check in xfcc authenticator (#41210)

https://github.com/istio/istioIstio AutomationSep 29, 2022via ghsa
2 files changed · +22 7
  • security/pkg/server/ca/authenticate/xfcc_authenticator.go+8 2 modified
    @@ -26,6 +26,7 @@ import (
     
     	"istio.io/istio/pilot/pkg/features"
     	"istio.io/istio/pkg/security"
    +	"istio.io/pkg/log"
     )
     
     const (
    @@ -92,13 +93,18 @@ func buildSecurityCaller(xfccHeader string) (*security.Caller, error) {
     }
     
     func isTrustedAddress(addr string, trustedCidrs []string) bool {
    +	ip, _, err := net.SplitHostPort(addr)
    +	if err != nil {
    +		log.Warnf("peer address %s can not be split in to proper host and port", addr)
    +		return false
    +	}
     	for _, cidr := range trustedCidrs {
    -		if isInRange(addr, cidr) {
    +		if isInRange(ip, cidr) {
     			return true
     		}
     	}
     	// Always trust local host addresses.
    -	return net.ParseIP(addr).IsLoopback()
    +	return net.ParseIP(ip).IsLoopback()
     }
     
     func isInRange(addr, cidr string) bool {
    
  • security/pkg/server/ca/authenticate/xfcc_authenticator_test.go+14 5 modified
    @@ -16,6 +16,7 @@ package authenticate
     
     import (
     	"net"
    +	"net/netip"
     	"reflect"
     	"strings"
     	"testing"
    @@ -36,11 +37,18 @@ func TestIsTrustedAddress(t *testing.T) {
     		trusted bool
     	}{
     		{
    -			name:    "localhost client",
    +			name:    "localhost client with port",
     			cidr:    "",
    -			peer:    "127.0.0.1",
    +			peer:    "127.0.0.1:9901",
     			trusted: true,
     		},
    +		{
    +			// Should never happen, added test case for testing it.
    +			name:    "localhost client without port",
    +			cidr:    "",
    +			peer:    "127.0.0.1",
    +			trusted: false,
    +		},
     		{
     			name:    "external client without trusted cidr",
     			cidr:    "",
    @@ -50,13 +58,13 @@ func TestIsTrustedAddress(t *testing.T) {
     		{
     			name:    "cidr in range",
     			cidr:    "172.17.0.0/16,192.17.0.0/16",
    -			peer:    "172.17.0.2",
    +			peer:    "172.17.0.2:9901",
     			trusted: true,
     		},
     		{
     			name:    "cidr in range with both ipv6 and ipv4",
     			cidr:    "172.17.0.0/16,2001:db8:1234:1a00::/56",
    -			peer:    "2001:0db8:1234:1aff:ffff:ffff:ffff:ffff",
    +			peer:    "[2001:0db8:1234:1a00:0000:0000:0000:0000]:80",
     			trusted: true,
     		},
     		{
    @@ -130,7 +138,8 @@ func TestXfccAuthenticator(t *testing.T) {
     			if len(tt.xfccHeader) > 0 {
     				md.Append(xfccparser.ForwardedClientCertHeader, tt.xfccHeader)
     			}
    -			ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: &net.IPAddr{IP: net.ParseIP("127.0.0.1").To4()}})
    +			addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:2301"))
    +			ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: addr})
     			ctx = metadata.NewIncomingContext(ctx, md)
     			result, err := auth.Authenticate(security.AuthContext{GrpcContext: ctx})
     			if len(tt.authenticateErrMsg) > 0 {
    
346260e5115e

add xfcc authenticator (#39405)

https://github.com/istio/istioRama ChavaliJul 14, 2022via ghsa
15 files changed · +393 53
  • go.mod+2 0 modified
    @@ -120,6 +120,8 @@ require (
     	github.com/PuerkitoBio/purell v1.1.1 // indirect
     	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
     	github.com/VividCortex/ewma v1.1.1 // indirect
    +	github.com/alecholmes/xfccparser v0.1.0
    +	github.com/alecthomas/participle v0.4.1 // indirect
     	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed // indirect
     	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
     	github.com/beorn7/perks v1.0.1 // indirect
    
  • go.sum+7 0 modified
    @@ -257,7 +257,14 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia
     github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
     github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
     github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8=
    +github.com/alecholmes/xfccparser v0.1.0 h1:/PBnzDBxfHJ66AinLNglzZH4oWLrc1/QTKlSoNNnei8=
    +github.com/alecholmes/xfccparser v0.1.0/go.mod h1:c1S35dudNR5aZ4Vf9zKCrEwC8iqwF4TcDAbU+RXQ5yY=
    +github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2/go.mod h1:CxCgO+NdpMdi9SsTlGbc0W+/UNxO3I0AabOEJZ3w61w=
     github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
    +github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
    +github.com/alecthomas/participle v0.4.1 h1:P2PJWzwrSpuCWXKnzqvw0b0phSfH1kJo4p2HvLynVsI=
    +github.com/alecthomas/participle v0.4.1/go.mod h1:T8u4bQOSMwrkTWOSyt8/jSFPEnRtd0FKFMjVfYBlqPs=
    +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
     github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
     github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
     github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
    
  • licenses/github.com/alecholmes/xfccparser/LICENSE+21 0 added
    @@ -0,0 +1,21 @@
    +MIT License
    +
    +Copyright (c) 2020 Alec Holmes
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    
  • licenses/github.com/alecthomas/participle/COPYING+19 0 added
    @@ -0,0 +1,19 @@
    +Copyright (C) 2017 Alec Thomas
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do
    +so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    
  • pilot/pkg/bootstrap/server.go+3 0 modified
    @@ -322,6 +322,9 @@ func NewServer(args *PilotArgs, initFuncs ...func(*Server)) (*Server, error) {
     	// so we build it later.
     	authenticators = append(authenticators,
     		kubeauth.NewKubeJWTAuthenticator(s.environment.Watcher, s.kubeClient.Kube(), s.clusterID, s.multiclusterController.GetRemoteKubeClient, features.JwtPolicy))
    +	if len(features.TrustedGatewayCIDR) > 0 {
    +		authenticators = append(authenticators, &authenticate.XfccAuthenticator{})
    +	}
     	if features.XDSAuth {
     		s.XDSServer.Authenticators = authenticators
     	}
    
  • pilot/pkg/features/pilot.go+12 0 modified
    @@ -405,6 +405,18 @@ var (
     		"If enabled, pilot will authorize XDS clients, to ensure they are acting only as namespaces they have permissions for.",
     	).Get()
     
    +	// TODO: Move this to proper API.
    +	trustedGatewayCIDR = env.RegisterStringVar(
    +		"TRUSTED_GATEWAY_CIDR",
    +		"",
    +		"If set, any connections from gateway to Istiod with this CIDR range are treated as trusted for using authenication mechanisms like XFCC."+
    +			" This can only be used when the network where Istiod and the authenticating gateways are running in a trusted/secure network",
    +	)
    +
    +	TrustedGatewayCIDR = func() []string {
    +		return strings.Split(trustedGatewayCIDR.Get(), ",")
    +	}()
    +
     	EnableServiceEntrySelectPods = env.RegisterBoolVar("PILOT_ENABLE_SERVICEENTRY_SELECT_PODS", true,
     		"If enabled, service entries with selectors will select pods from the cluster. "+
     			"It is safe to disable it if you are quite sure you don't need this feature").Get()
    
  • pilot/pkg/xds/auth.go+9 12 modified
    @@ -18,7 +18,6 @@ import (
     	"context"
     	"errors"
     	"fmt"
    -	"strings"
     
     	"google.golang.org/grpc/codes"
     	"google.golang.org/grpc/credentials"
    @@ -27,12 +26,13 @@ import (
     
     	"istio.io/istio/pilot/pkg/features"
     	"istio.io/istio/pilot/pkg/model"
    +	"istio.io/istio/pkg/security"
     	"istio.io/istio/pkg/spiffe"
     	"istio.io/pkg/env"
     )
     
     var AuthPlaintext = env.RegisterBoolVar("XDS_AUTH_PLAINTEXT", false,
    -	"Authenticate plain text requests - used if Istiod is behind a gateway handling TLS").Get()
    +	"Authenticate plain text requests - used if Istiod is running on a secure/trusted network").Get()
     
     // authenticate authenticates the ADS request using the configured authenticators.
     // Returns the validated principals or an error.
    @@ -56,17 +56,14 @@ func (s *DiscoveryServer) authenticate(ctx context.Context) ([]string, error) {
     	if _, ok := peerInfo.AuthInfo.(credentials.TLSInfo); !ok && !AuthPlaintext {
     		return nil, nil
     	}
    -	authFailMsgs := []string{}
    -	for _, authn := range s.Authenticators {
    -		u, err := authn.Authenticate(ctx)
    -		// If one authenticator passes, return
    -		if u != nil && u.Identities != nil && err == nil {
    -			return u.Identities, nil
    -		}
    -		authFailMsgs = append(authFailMsgs, fmt.Sprintf("Authenticator %s: %v", authn.AuthenticatorType(), err))
    -	}
     
    -	log.Errorf("Failed to authenticate client from %s: %s", peerInfo.Addr.String(), strings.Join(authFailMsgs, "; "))
    +	am := security.AuthenticationManager{
    +		Authenticators: s.Authenticators,
    +	}
    +	if u := am.Authenticate(ctx); u != nil {
    +		return u.Identities, nil
    +	}
    +	log.Errorf("Failed to authenticate client from %s: %s", peerInfo.Addr.String(), am.FailedMessages())
     	return nil, errors.New("authentication failure")
     }
     
    
  • pkg/security/security.go+37 0 modified
    @@ -23,11 +23,14 @@ import (
     	"time"
     
     	"google.golang.org/grpc/metadata"
    +	"google.golang.org/grpc/peer"
     
     	"istio.io/pkg/env"
     	istiolog "istio.io/pkg/log"
     )
     
    +var securityLog = istiolog.RegisterScope("security", "security debugging", 0)
    +
     const (
     	// etc/certs files are used with external CA managing the certs,
     	// i.e. mounted Secret or external plugin.
    @@ -351,12 +354,46 @@ type Caller struct {
     	Identities []string
     }
     
    +// Authenticator determines the caller identity based on request context.
     type Authenticator interface {
     	Authenticate(ctx context.Context) (*Caller, error)
     	AuthenticatorType() string
     	AuthenticateRequest(req *http.Request) (*Caller, error)
     }
     
    +// AuthenticationManager orchestrates all authenticators to perform authentication.
    +type AuthenticationManager struct {
    +	Authenticators []Authenticator
    +	// authFailMsgs contains list of messages that authenticator wants to record - mainly used for logging.
    +	authFailMsgs []string
    +}
    +
    +// Authenticate loops through all the configured Authenticators and returns if one of the authenticator succeeds.
    +func (am *AuthenticationManager) Authenticate(ctx context.Context) *Caller {
    +	for _, authn := range am.Authenticators {
    +		u, err := authn.Authenticate(ctx)
    +		if u != nil && len(u.Identities) > 0 && err == nil {
    +			securityLog.Debugf("Authentication successful through auth source %v", u.AuthSource)
    +			return u
    +		}
    +		am.authFailMsgs = append(am.authFailMsgs, fmt.Sprintf("Authenticator %s: %v", authn.AuthenticatorType(), err))
    +	}
    +	return nil
    +}
    +
    +func GetConnectionAddress(ctx context.Context) string {
    +	peerInfo, ok := peer.FromContext(ctx)
    +	peerAddr := "unknown"
    +	if ok {
    +		peerAddr = peerInfo.Addr.String()
    +	}
    +	return peerAddr
    +}
    +
    +func (am *AuthenticationManager) FailedMessages() string {
    +	return strings.Join(am.authFailMsgs, "; ")
    +}
    +
     func ExtractBearerToken(ctx context.Context) (string, error) {
     	md, ok := metadata.FromIncomingContext(ctx)
     	if !ok {
    
  • security/pkg/nodeagent/caclient/providers/citadel/client_test.go+2 2 modified
    @@ -44,7 +44,6 @@ import (
     	"istio.io/istio/security/pkg/credentialfetcher/plugin"
     	"istio.io/istio/security/pkg/monitoring"
     	"istio.io/istio/security/pkg/nodeagent/util"
    -	ca2 "istio.io/istio/security/pkg/server/ca"
     )
     
     const (
    @@ -67,7 +66,8 @@ type mockCAServer struct {
     
     func (ca *mockCAServer) CreateCertificate(ctx context.Context, in *pb.IstioCertificateRequest) (*pb.IstioCertificateResponse, error) {
     	if ca.Authenticator != nil {
    -		caller := ca2.Authenticate(ctx, []security.Authenticator{ca.Authenticator})
    +		am := security.AuthenticationManager{Authenticators: []security.Authenticator{ca.Authenticator}}
    +		caller := am.Authenticate(ctx)
     		if caller == nil {
     			return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
     		}
    
  • security/pkg/nodeagent/test/mock/caserver.go+3 2 modified
    @@ -32,7 +32,6 @@ import (
     	"istio.io/istio/pkg/spiffe"
     	caerror "istio.io/istio/security/pkg/pki/error"
     	"istio.io/istio/security/pkg/pki/util"
    -	"istio.io/istio/security/pkg/server/ca"
     	"istio.io/pkg/log"
     )
     
    @@ -166,8 +165,10 @@ func (s *CAServer) CreateCertificate(ctx context.Context, request *pb.IstioCerti
     	}
     	id := []string{"client-identity"}
     	if len(s.Authenticators) > 0 {
    -		caller := ca.Authenticate(ctx, s.Authenticators)
    +		am := security.AuthenticationManager{Authenticators: s.Authenticators}
    +		caller := am.Authenticate(ctx)
     		if caller == nil {
    +			caServerLog.Errorf("Failed to authenticate client from %s: %s", security.GetConnectionAddress(ctx), am.FailedMessages())
     			return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
     		}
     		id = caller.Identities
    
  • security/pkg/server/ca/authenticate/cert_authenticator.go+1 1 modified
    @@ -15,10 +15,10 @@
     package authenticate
     
     import (
    +	"context"
     	"fmt"
     	"net/http"
     
    -	"golang.org/x/net/context"
     	"google.golang.org/grpc/credentials"
     	"google.golang.org/grpc/peer"
     
    
  • security/pkg/server/ca/authenticate/xfcc_authenticator.go+117 0 added
    @@ -0,0 +1,117 @@
    +// Copyright Istio Authors
    +//
    +// 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 authenticate
    +
    +import (
    +	"context"
    +	"fmt"
    +	"net"
    +	"net/http"
    +	"strings"
    +
    +	"github.com/alecholmes/xfccparser"
    +	"google.golang.org/grpc/metadata"
    +	"google.golang.org/grpc/peer"
    +
    +	"istio.io/istio/pilot/pkg/features"
    +	"istio.io/istio/pkg/security"
    +)
    +
    +const (
    +	XfccAuthenticatorType = "XfccAuthenticator"
    +)
    +
    +// XfccAuthenticator extracts identities from Xfcc header.
    +type XfccAuthenticator struct{}
    +
    +var _ security.Authenticator = &XfccAuthenticator{}
    +
    +func (xff XfccAuthenticator) AuthenticatorType() string {
    +	return XfccAuthenticatorType
    +}
    +
    +// Authenticate extracts identities from Xfcc Header.
    +func (xff XfccAuthenticator) Authenticate(ctx context.Context) (*security.Caller, error) {
    +	peerInfo, _ := peer.FromContext(ctx)
    +	// First check if client is trusted client so that we can "trust" the Xfcc Header.
    +	if !isTrustedAddress(peerInfo.Addr.String(), features.TrustedGatewayCIDR) {
    +		return nil, fmt.Errorf("caller from %s is not in the trusted network. XfccAuthenticator can not be used", peerInfo.Addr.String())
    +	}
    +	meta, ok := metadata.FromIncomingContext(ctx)
    +
    +	if !ok || len(meta.Get(xfccparser.ForwardedClientCertHeader)) == 0 {
    +		return nil, nil
    +	}
    +	xfccHeader := meta.Get(xfccparser.ForwardedClientCertHeader)[0]
    +	return buildSecurityCaller(xfccHeader)
    +}
    +
    +// AuthenticateRequest validates Xfcc Header.
    +func (xff XfccAuthenticator) AuthenticateRequest(req *http.Request) (*security.Caller, error) {
    +	xfccHeader := req.Header.Get(xfccparser.ForwardedClientCertHeader)
    +	if len(xfccHeader) == 0 {
    +		return nil, nil
    +	}
    +	return buildSecurityCaller(xfccHeader)
    +}
    +
    +func buildSecurityCaller(xfccHeader string) (*security.Caller, error) {
    +	clientCerts, err := xfccparser.ParseXFCCHeader(xfccHeader)
    +	if err != nil {
    +		message := fmt.Sprintf("error in parsing xfcc header: %v", err)
    +		return nil, fmt.Errorf(message)
    +	}
    +	if len(clientCerts) == 0 {
    +		message := "xfcc header does not have atleast one client certs"
    +		return nil, fmt.Errorf(message)
    +	}
    +	ids := []string{}
    +	for _, cc := range clientCerts {
    +		ids = append(ids, cc.URI)
    +		ids = append(ids, cc.DNS...)
    +		if cc.Subject != nil {
    +			ids = append(ids, cc.Subject.CommonName)
    +		}
    +	}
    +
    +	return &security.Caller{
    +		AuthSource: security.AuthSourceClientCertificate,
    +		Identities: ids,
    +	}, nil
    +}
    +
    +func isTrustedAddress(addr string, trustedCidrs []string) bool {
    +	for _, cidr := range trustedCidrs {
    +		if isInRange(addr, cidr) {
    +			return true
    +		}
    +	}
    +	// Always trust local host addresses.
    +	return net.ParseIP(addr).IsLoopback()
    +}
    +
    +func isInRange(addr, cidr string) bool {
    +	if strings.Contains(cidr, "/") {
    +		ip, ipnet, err := net.ParseCIDR(cidr)
    +		if err != nil {
    +			return false
    +		}
    +		if ip.To4() == nil && ip.To16() == nil {
    +			return false
    +		}
    +		return ipnet.Contains(net.ParseIP(addr))
    +	}
    +	return false
    +}
    
  • security/pkg/server/ca/authenticate/xfcc_authenticator_test.go+153 0 added
    @@ -0,0 +1,153 @@
    +// Copyright Istio Authors
    +//
    +// 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 authenticate
    +
    +import (
    +	"net"
    +	"reflect"
    +	"strings"
    +	"testing"
    +
    +	"github.com/alecholmes/xfccparser"
    +	"golang.org/x/net/context"
    +	"google.golang.org/grpc/metadata"
    +	"google.golang.org/grpc/peer"
    +
    +	"istio.io/istio/pkg/security"
    +)
    +
    +func TestIsTrustedAddress(t *testing.T) {
    +	cases := []struct {
    +		name    string
    +		cidr    string
    +		peer    string
    +		trusted bool
    +	}{
    +		{
    +			name:    "localhost client",
    +			cidr:    "",
    +			peer:    "127.0.0.1",
    +			trusted: true,
    +		},
    +		{
    +			name:    "external client without trusted cidr",
    +			cidr:    "",
    +			peer:    "172.0.0.1",
    +			trusted: false,
    +		},
    +		{
    +			name:    "cidr in range",
    +			cidr:    "172.17.0.0/16,192.17.0.0/16",
    +			peer:    "172.17.0.2",
    +			trusted: true,
    +		},
    +		{
    +			name:    "cidr in range with both ipv6 and ipv4",
    +			cidr:    "172.17.0.0/16,2001:db8:1234:1a00::/56",
    +			peer:    "2001:0db8:1234:1aff:ffff:ffff:ffff:ffff",
    +			trusted: true,
    +		},
    +		{
    +			name:    "cidr outside range",
    +			cidr:    "172.17.0.0/16,172.17.0.0/16",
    +			peer:    "110.17.0.2",
    +			trusted: false,
    +		},
    +	}
    +
    +	for _, tt := range cases {
    +		t.Run(tt.name, func(t *testing.T) {
    +			if result := isTrustedAddress(tt.peer, strings.Split(tt.cidr, ",")); result != tt.trusted {
    +				t.Errorf("Unexpected authentication result: want %v but got %v",
    +					tt.trusted, result)
    +			}
    +		})
    +	}
    +}
    +
    +func TestXfccAuthenticator(t *testing.T) {
    +	cases := []struct {
    +		name               string
    +		xfccHeader         string
    +		caller             *security.Caller
    +		authenticateErrMsg string
    +	}{
    +		{
    +			name:       "No xfcc header",
    +			xfccHeader: "",
    +			caller:     nil,
    +		},
    +		{
    +			name:               "junk xfcc header",
    +			xfccHeader:         `junk xfcc header`,
    +			authenticateErrMsg: `error in parsing xfcc header: invalid header format: unexpected token "junk xfcc header"`,
    +		},
    +		{
    +			name: "Xfcc Header single hop",
    +			// nolint lll
    +			xfccHeader: `Hash=meshclient;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
    +			caller: &security.Caller{
    +				AuthSource: security.AuthSourceClientCertificate,
    +				Identities: []string{
    +					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
    +				},
    +			},
    +		},
    +		{
    +			name: "Xfcc Header multiple hops",
    +			// nolint lll
    +			xfccHeader: `Hash=hash;Cert="-----BEGIN%20CERTIFICATE-----%0cert%0A-----END%20CERTIFICATE-----%0A";Subject="CN=hello,OU=hello,O=Acme\, Inc.";URI=spiffe://mesh.example.com/ns/firstns/sa/firstsa;DNS=hello.west.example.com;DNS=hello.east.example.com,By=spiffe://mesh.example.com/ns/hellons/sa/hellosa;Hash=again;Subject="";URI=spiffe://mesh.example.com/ns/otherns/sa/othersa`,
    +			caller: &security.Caller{
    +				AuthSource: security.AuthSourceClientCertificate,
    +				Identities: []string{
    +					"spiffe://mesh.example.com/ns/firstns/sa/firstsa",
    +					"hello.west.example.com",
    +					"hello.east.example.com",
    +					"hello",
    +					"spiffe://mesh.example.com/ns/otherns/sa/othersa",
    +				},
    +			},
    +		},
    +	}
    +
    +	auth := &XfccAuthenticator{}
    +
    +	for _, tt := range cases {
    +		t.Run(tt.name, func(t *testing.T) {
    +			md := metadata.MD{}
    +			if len(tt.xfccHeader) > 0 {
    +				md.Append(xfccparser.ForwardedClientCertHeader, tt.xfccHeader)
    +			}
    +			ctx := peer.NewContext(context.Background(), &peer.Peer{Addr: &net.IPAddr{IP: net.ParseIP("127.0.0.1").To4()}})
    +			ctx = metadata.NewIncomingContext(ctx, md)
    +			result, err := auth.Authenticate(ctx)
    +			if len(tt.authenticateErrMsg) > 0 {
    +				if err == nil {
    +					t.Errorf("Succeeded. Error expected: %v", err)
    +				} else if err.Error() != tt.authenticateErrMsg {
    +					t.Errorf("Incorrect error message: want %s but got %s",
    +						tt.authenticateErrMsg, err.Error())
    +				}
    +			} else if err != nil {
    +				t.Fatalf("Unexpected Error: %v", err)
    +			}
    +
    +			if !reflect.DeepEqual(tt.caller, result) {
    +				t.Errorf("Unexpected authentication result: want %v but got %v",
    +					tt.caller, result)
    +			}
    +		})
    +	}
    +}
    
  • security/pkg/server/ca/server.go+2 31 modified
    @@ -15,13 +15,11 @@
     package ca
     
     import (
    -	"fmt"
     	"time"
     
     	"golang.org/x/net/context"
     	"google.golang.org/grpc"
     	"google.golang.org/grpc/codes"
    -	"google.golang.org/grpc/peer"
     	"google.golang.org/grpc/status"
     
     	pb "istio.io/api/security/v1alpha1"
    @@ -54,15 +52,6 @@ type Server struct {
     	serverCertTTL  time.Duration
     }
     
    -func getConnectionAddress(ctx context.Context) string {
    -	peerInfo, ok := peer.FromContext(ctx)
    -	peerAddr := "unknown"
    -	if ok {
    -		peerAddr = peerInfo.Addr.String()
    -	}
    -	return peerAddr
    -}
    -
     // CreateCertificate handles an incoming certificate signing request (CSR). It does
     // authentication and authorization. Upon validated, signs a certificate that:
     // the SAN is the identity of the caller in authentication result.
    @@ -73,7 +62,8 @@ func (s *Server) CreateCertificate(ctx context.Context, request *pb.IstioCertifi
     	*pb.IstioCertificateResponse, error,
     ) {
     	s.monitoring.CSR.Increment()
    -	caller := Authenticate(ctx, s.Authenticators)
    +	am := security.AuthenticationManager{Authenticators: s.Authenticators}
    +	caller := am.Authenticate(ctx)
     	if caller == nil {
     		s.monitoring.AuthnError.Increment()
     		return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
    @@ -158,22 +148,3 @@ func New(ca CertificateAuthority, ttl time.Duration,
     	}
     	return server, nil
     }
    -
    -// authenticate goes through a list of authenticators (provided client cert, k8s jwt, and ID token)
    -// and authenticates if one of them is valid.
    -func Authenticate(ctx context.Context, auth []security.Authenticator) *security.Caller {
    -	// TODO: apply different authenticators in specific order / according to configuration.
    -	var errMsg string
    -	for id, authn := range auth {
    -		u, err := authn.Authenticate(ctx)
    -		if err != nil {
    -			errMsg += fmt.Sprintf("Authenticator %s at index %d got error: %v. ", authn.AuthenticatorType(), id, err)
    -		}
    -		if u != nil && err == nil {
    -			serverCaLog.Debugf("Authentication successful through auth source %v", u.AuthSource)
    -			return u
    -		}
    -	}
    -	serverCaLog.Warnf("Authentication failed for %v: %s", getConnectionAddress(ctx), errMsg)
    -	return nil
    -}
    
  • security/pkg/server/ca/server_test.go+5 5 modified
    @@ -205,27 +205,27 @@ func TestCreateCertificate(t *testing.T) {
     			ca:   &mockca.FakeCA{},
     		},
     		"CA not ready": {
    -			authenticators: []security.Authenticator{&mockAuthenticator{}},
    +			authenticators: []security.Authenticator{&mockAuthenticator{identities: []string{"test-identity"}}},
     			ca:             &mockca.FakeCA{SignErr: caerror.NewError(caerror.CANotReady, fmt.Errorf("cannot sign"))},
     			code:           codes.Internal,
     		},
     		"Invalid CSR": {
    -			authenticators: []security.Authenticator{&mockAuthenticator{}},
    +			authenticators: []security.Authenticator{&mockAuthenticator{identities: []string{"test-identity"}}},
     			ca:             &mockca.FakeCA{SignErr: caerror.NewError(caerror.CSRError, fmt.Errorf("cannot sign"))},
     			code:           codes.InvalidArgument,
     		},
     		"Invalid TTL": {
    -			authenticators: []security.Authenticator{&mockAuthenticator{}},
    +			authenticators: []security.Authenticator{&mockAuthenticator{identities: []string{"test-identity"}}},
     			ca:             &mockca.FakeCA{SignErr: caerror.NewError(caerror.TTLError, fmt.Errorf("cannot sign"))},
     			code:           codes.InvalidArgument,
     		},
     		"Failed to sign": {
    -			authenticators: []security.Authenticator{&mockAuthenticator{}},
    +			authenticators: []security.Authenticator{&mockAuthenticator{identities: []string{"test-identity"}}},
     			ca:             &mockca.FakeCA{SignErr: caerror.NewError(caerror.CertGenError, fmt.Errorf("cannot sign"))},
     			code:           codes.Internal,
     		},
     		"Successful signing": {
    -			authenticators: []security.Authenticator{&mockAuthenticator{}},
    +			authenticators: []security.Authenticator{&mockAuthenticator{identities: []string{"test-identity"}}},
     			ca: &mockca.FakeCA{
     				SignedCert:    []byte("cert"),
     				KeyCertBundle: util.NewKeyCertBundleFromPem(nil, nil, []byte("cert_chain"), []byte("root_cert")),
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.