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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/istio/istioGo | >= 1.15.0-beta.0, < 1.15.3 | 1.15.3 |
Affected products
1Patches
29a643e270421[release-1.15] fix localhost address check in xfcc authenticator (#41210)
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 {
346260e5115eadd xfcc authenticator (#39405)
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- github.com/advisories/GHSA-6c6p-h79f-g6p4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-39388ghsaADVISORY
- github.com/istio/istio/commit/346260e5115e9fbc65ba8a559bc686e6ca046a32ghsaWEB
- github.com/istio/istio/commit/9a643e270421560afb2630e00f76d46a55499df9ghsaWEB
- github.com/istio/istio/security/advisories/GHSA-6c6p-h79f-g6p4ghsaWEB
- istio.io/latest/news/releases/1.15.x/announcing-1.15.3ghsaWEB
- istio.io/latest/news/releases/1.15.x/announcing-1.15.3/mitre
News mentions
0No linked articles in our index yet.