Skip to content

Commit b40623f

Browse files
author
github-actions
committed
Merge tag '1.28.5' into tetratefips-release-1.28
Istio release 1.28.5
2 parents 7178af8 + 7da6662 commit b40623f

35 files changed

Lines changed: 960 additions & 69 deletions

Makefile.core.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ endif
4949
export VERSION
5050

5151
# Base version of Istio image to use
52-
BASE_VERSION ?= 1.28-2026-02-05T19-01-07
52+
BASE_VERSION ?= 1.28-2026-02-26T19-03-31
5353
ISTIO_BASE_REGISTRY ?= gcr.io/istio-release
5454

5555
export GO111MODULE ?= on

istio.deps

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"name": "PROXY_REPO_SHA",
55
"repoName": "proxy",
66
"file": "",
7-
"lastStableSHA": "cc703bb68e23c81898b82a0205549300295851e8"
7+
"lastStableSHA": "21738f7c7009a5e8309bf002d546e118233f17b1"
88
},
99
{
1010
"_comment": "",

pilot/pkg/config/kube/gateway/backend_policies.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ func DestinationRuleCollection(
237237
}
238238
}
239239
case kind.ServiceEntry:
240-
serviceEntryObj, err := references.LocalPolicyTargetRef(gw.LocalPolicyTargetReference{
240+
serviceEntryObj, err := references.LocalPolicyTargetRef(ctx, gw.LocalPolicyTargetReference{
241241
Group: "networking.istio.io",
242242
Kind: "ServiceEntry",
243243
Name: gw.ObjectName(target.Name),
@@ -317,7 +317,7 @@ func BackendTLSPolicyCollection(
317317
}
318318
return nil
319319
})
320-
tls.CredentialName = getBackendTLSCredentialName(s.Validation, i.Namespace, conds, references)
320+
tls.CredentialName = getBackendTLSCredentialName(ctx, s.Validation, i.Namespace, conds, references)
321321

322322
// In ancestor status, we need to report for Service (for mesh) and for each relevant Gateway.
323323
// However, there is a max of 16 items we can report.
@@ -329,7 +329,7 @@ func BackendTLSPolicyCollection(
329329
uniqueGateways := sets.New[types.NamespacedName]()
330330
for idx, t := range i.Spec.TargetRefs {
331331
conds = maps.Clone(conds)
332-
refo, err := references.LocalPolicyTargetRef(t.LocalPolicyTargetReference, i.Namespace)
332+
refo, err := references.LocalPolicyTargetRef(ctx, t.LocalPolicyTargetReference, i.Namespace)
333333
var sectionName *string
334334
if err == nil {
335335
switch refType := refo.(type) {
@@ -433,6 +433,7 @@ func BackendTLSPolicyCollection(
433433
}
434434

435435
func getBackendTLSCredentialName(
436+
ctx krt.HandlerContext,
436437
validation gw.BackendTLSPolicyValidation,
437438
policyNamespace string,
438439
conds map[string]*condition,
@@ -460,7 +461,7 @@ func getBackendTLSCredentialName(
460461
if len(validation.CACertificateRefs) > 1 {
461462
conds[string(gw.PolicyConditionAccepted)].message += "; warning: only the first caCertificateRefs will be used"
462463
}
463-
refo, err := references.LocalPolicyRef(ref, policyNamespace)
464+
refo, err := references.LocalPolicyRef(ctx, ref, policyNamespace)
464465
if err == nil {
465466
switch to := refo.(type) {
466467
case *v1.ConfigMap:
@@ -557,7 +558,7 @@ func BackendTrafficPolicyCollection(
557558

558559
for idx, t := range i.Spec.TargetRefs {
559560
conds = maps.Clone(conds)
560-
refo, err := references.XLocalPolicyTargetRef(t, i.Namespace)
561+
refo, err := references.XLocalPolicyTargetRef(ctx, t, i.Namespace)
561562
if err == nil {
562563
switch refo.(type) {
563564
case *v1.Service:

pilot/pkg/config/kube/gateway/conversion_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,30 @@ var services = []*model.Service{
158158
Ports: inferencePoolPorts,
159159
Hostname: host.Name(fmt.Sprintf("%s.default.svc.domain.suffix", firstValue(InferencePoolServiceName("infpool-gen2")))),
160160
},
161+
{
162+
Attributes: model.ServiceAttributes{
163+
Namespace: "default",
164+
Labels: map[string]string{
165+
InferencePoolExtensionRefSvc: "model1-epp",
166+
InferencePoolExtensionRefPort: "9002",
167+
InferencePoolExtensionRefFailureMode: "FailClose",
168+
},
169+
},
170+
Ports: ports,
171+
Hostname: host.Name(fmt.Sprintf("%s.default.svc.domain.suffix", firstValue(InferencePoolServiceName("infpool-model1")))),
172+
},
173+
{
174+
Attributes: model.ServiceAttributes{
175+
Namespace: "default",
176+
Labels: map[string]string{
177+
InferencePoolExtensionRefSvc: "model2-epp",
178+
InferencePoolExtensionRefPort: "9002",
179+
InferencePoolExtensionRefFailureMode: "FailClose",
180+
},
181+
},
182+
Ports: ports,
183+
Hostname: host.Name(fmt.Sprintf("%s.default.svc.domain.suffix", firstValue(InferencePoolServiceName("infpool-model2")))),
184+
},
161185

162186
{
163187
Attributes: model.ServiceAttributes{

pilot/pkg/config/kube/gateway/references.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,35 @@ import (
2929
// ReferenceSet stores a variety of different types of resource, and allows looking them up as Gateway API references.
3030
// This is merely a convenience to avoid needing to lookup up a bunch of types all over the place.
3131
type ReferenceSet struct {
32-
erasedCollections map[config.GroupVersionKind]func(name, namespace string) (any, bool)
32+
erasedCollections map[config.GroupVersionKind]func(ctx krt.HandlerContext, name, namespace string) (any, bool)
3333
}
3434

35-
func (s ReferenceSet) LocalPolicyTargetRef(ref gatewayv1.LocalPolicyTargetReference, localNamespace string) (any, error) {
36-
return s.internal(string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
35+
func (s ReferenceSet) LocalPolicyTargetRef(ctx krt.HandlerContext, ref gatewayv1.LocalPolicyTargetReference, localNamespace string) (any, error) {
36+
return s.internal(ctx, string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
3737
}
3838

39-
func (s ReferenceSet) XLocalPolicyTargetRef(ref gatewayx.LocalPolicyTargetReference, localNamespace string) (any, error) {
40-
return s.internal(string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
39+
func (s ReferenceSet) XLocalPolicyTargetRef(ctx krt.HandlerContext, ref gatewayx.LocalPolicyTargetReference, localNamespace string) (any, error) {
40+
return s.internal(ctx, string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
4141
}
4242

43-
func (s ReferenceSet) LocalPolicyRef(ref gatewayv1.LocalObjectReference, localNamespace string) (any, error) {
44-
return s.internal(string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
43+
func (s ReferenceSet) LocalPolicyRef(ctx krt.HandlerContext, ref gatewayv1.LocalObjectReference, localNamespace string) (any, error) {
44+
return s.internal(ctx, string(ref.Name), string(ref.Group), string(ref.Kind), localNamespace)
4545
}
4646

47-
func (s ReferenceSet) internal(name, group, kind, localNamespace string) (any, error) {
47+
func (s ReferenceSet) internal(ctx krt.HandlerContext, name, group, kind, localNamespace string) (any, error) {
4848
t := normalizeReference(&group, &kind, config.GroupVersionKind{})
4949
lookup, f := s.erasedCollections[t]
5050
if !f {
5151
return nil, fmt.Errorf("unsupported kind %v", kind)
5252
}
53-
if v, ok := lookup(name, localNamespace); ok {
53+
if v, ok := lookup(ctx, name, localNamespace); ok {
5454
return v, nil
5555
}
5656
return nil, fmt.Errorf("reference %v/%v (of kind %v) not found", localNamespace, name, kind)
5757
}
5858

5959
func NewReferenceSet(opts ...func(r *ReferenceSet)) *ReferenceSet {
60-
r := &ReferenceSet{erasedCollections: make(map[config.GroupVersionKind]func(name, namespace string) (any, bool))}
60+
r := &ReferenceSet{erasedCollections: make(map[config.GroupVersionKind]func(ctx krt.HandlerContext, name, namespace string) (any, bool))}
6161
for _, opt := range opts {
6262
opt(r)
6363
}
@@ -67,8 +67,8 @@ func NewReferenceSet(opts ...func(r *ReferenceSet)) *ReferenceSet {
6767
func AddReference[T runtime.Object](c krt.Collection[T]) func(r *ReferenceSet) {
6868
return func(r *ReferenceSet) {
6969
g := schematypes.MustGVKFromType[T]()
70-
r.erasedCollections[g] = func(name, namespace string) (any, bool) {
71-
o := c.GetKey(namespace + "/" + name)
70+
r.erasedCollections[g] = func(ctx krt.HandlerContext, name, namespace string) (any, bool) {
71+
o := krt.FetchOne(ctx, c, krt.FilterKey(namespace+"/"+name))
7272
if o == nil {
7373
return nil, false
7474
}

pilot/pkg/config/kube/gateway/route_collections.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,12 +813,63 @@ func mergeHTTPRoutes(baseVirtualServices krt.Collection[RouteWithKey], opts ...k
813813
sortRoutesByCreationTime(configs)
814814
base := configs[0].DeepCopy()
815815
baseVS := base.Spec.(*istio.VirtualService)
816-
for _, config := range configs[1:] {
816+
// Deep copy the InferencePool configs map to avoid race conditions
817+
// The default DeepCopy() only does shallow copy of Extra field
818+
if base.Extra != nil {
819+
if ipConfigs, ok := base.Extra[constants.ConfigExtraPerRouteRuleInferencePoolConfigs].(map[string]kube.InferencePoolRouteRuleConfig); ok {
820+
// Create a new map to avoid modifying the shared underlying map
821+
newIPConfigs := make(map[string]kube.InferencePoolRouteRuleConfig, len(ipConfigs))
822+
for k, v := range ipConfigs {
823+
newIPConfigs[k] = v
824+
}
825+
base.Extra[constants.ConfigExtraPerRouteRuleInferencePoolConfigs] = newIPConfigs
826+
}
827+
}
828+
for i, config := range configs[1:] {
817829
thisVS := config.Spec.(*istio.VirtualService)
818830
baseVS.Http = append(baseVS.Http, thisVS.Http...)
819831
// append parents
820832
base.Annotations[constants.InternalParentNames] = fmt.Sprintf("%s,%s",
821833
base.Annotations[constants.InternalParentNames], config.Annotations[constants.InternalParentNames])
834+
// Merge Extra field (especially for InferencePool configs)
835+
if base.Extra == nil && config.Extra != nil {
836+
base.Extra = make(map[string]any)
837+
}
838+
if config.Extra != nil {
839+
for k, v := range config.Extra {
840+
// For non-InferencePool configs, keep the first value for stability
841+
if k != constants.ConfigExtraPerRouteRuleInferencePoolConfigs {
842+
if _, exists := base.Extra[k]; !exists {
843+
base.Extra[k] = v
844+
}
845+
continue
846+
}
847+
// For InferencePool configs, merge the maps
848+
baseMap, baseOk := base.Extra[k].(map[string]kube.InferencePoolRouteRuleConfig)
849+
configMap, configOk := v.(map[string]kube.InferencePoolRouteRuleConfig)
850+
if baseOk && configOk {
851+
log.Debugf("Merging InferencePool configs: adding %d route configs from VirtualService %d to base (namespace=%s)",
852+
len(configMap), i+1, config.Namespace)
853+
// Route names are composed of the HTTPRoute/VirtualService namespaced name so they can't possibly conflict
854+
for routeName, routeConfig := range configMap {
855+
baseMap[routeName] = routeConfig
856+
}
857+
} else if configOk {
858+
if _, exists := base.Extra[k]; !exists {
859+
log.Debugf("Creating new InferencePool config map from VirtualService %d (namespace=%s)", i+1, config.Namespace)
860+
base.Extra[k] = v
861+
}
862+
} else if !configOk {
863+
log.Debugf("Skipping InferencePool config from VirtualService %d due to unexpected type (namespace=%s)", i+1, config.Namespace)
864+
}
865+
}
866+
}
867+
}
868+
// Log final merged InferencePool configs
869+
if base.Extra != nil {
870+
if ipConfigs, ok := base.Extra[constants.ConfigExtraPerRouteRuleInferencePoolConfigs].(map[string]kube.InferencePoolRouteRuleConfig); ok {
871+
log.Debugf("Final merged VirtualService for key %s has %d InferencePool route configs", object.Key, len(ipConfigs))
872+
}
822873
}
823874
sortHTTPRoutes(baseVS.Http)
824875
base.Name = strings.ReplaceAll(object.Key, "/", "~")

pilot/pkg/config/kube/gateway/testdata/http.status.yaml.golden

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ metadata:
1414
spec: null
1515
status: {}
1616
---
17+
apiVersion: inference.networking.k8s.io/v1
18+
kind: InferencePool
19+
metadata:
20+
name: infpool-model1
21+
namespace: default
22+
spec: null
23+
status: {}
24+
---
25+
apiVersion: inference.networking.k8s.io/v1
26+
kind: InferencePool
27+
metadata:
28+
name: infpool-model2
29+
namespace: default
30+
spec: null
31+
status: {}
32+
---
1733
apiVersion: gateway.networking.k8s.io/v1beta1
1834
kind: GatewayClass
1935
metadata:

pilot/pkg/config/kube/gateway/testdata/http.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,3 +423,79 @@ spec:
423423
name: vllm-llama3-8b-instruct-epp
424424
port:
425425
number: 9002
426+
---
427+
# Test case for multiple HTTPRoutes with InferencePools on same gateway
428+
# This tests that Extra field (InferencePool configs) are properly merged
429+
apiVersion: gateway.networking.k8s.io/v1
430+
kind: HTTPRoute
431+
metadata:
432+
name: multi-route-infpool-1
433+
namespace: default
434+
spec:
435+
parentRefs:
436+
- name: gateway
437+
namespace: istio-system
438+
hostnames: ["multi-infpool.domain.example"]
439+
rules:
440+
- matches:
441+
- path:
442+
type: PathPrefix
443+
value: /model1
444+
backendRefs:
445+
- name: infpool-model1
446+
group: inference.networking.k8s.io
447+
kind: InferencePool
448+
port: 80
449+
---
450+
apiVersion: gateway.networking.k8s.io/v1
451+
kind: HTTPRoute
452+
metadata:
453+
name: multi-route-infpool-2
454+
namespace: default
455+
spec:
456+
parentRefs:
457+
- name: gateway
458+
namespace: istio-system
459+
hostnames: ["multi-infpool.domain.example"]
460+
rules:
461+
- matches:
462+
- path:
463+
type: PathPrefix
464+
value: /model2
465+
backendRefs:
466+
- name: infpool-model2
467+
group: inference.networking.k8s.io
468+
kind: InferencePool
469+
port: 80
470+
---
471+
apiVersion: inference.networking.k8s.io/v1
472+
kind: InferencePool
473+
metadata:
474+
name: infpool-model1
475+
namespace: default
476+
spec:
477+
targetPorts:
478+
- number: 8000
479+
selector:
480+
matchLabels:
481+
app: model1-server
482+
endpointPickerRef:
483+
name: model1-epp
484+
port:
485+
number: 9002
486+
---
487+
apiVersion: inference.networking.k8s.io/v1
488+
kind: InferencePool
489+
metadata:
490+
name: infpool-model2
491+
namespace: default
492+
spec:
493+
targetPorts:
494+
- number: 8000
495+
selector:
496+
matchLabels:
497+
app: model2-server
498+
endpointPickerRef:
499+
name: model2-epp
500+
port:
501+
number: 9002

pilot/pkg/model/jwks_resolver.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,10 @@ func (r *JwksResolver) BuildLocalJwks(jwksURI, jwtIssuer, jwtPubKey string, time
295295
// workflow, where the JWT key resolver should have already been initialized on server creation.
296296
jwtPubKey, err = r.GetPublicKey(jwtIssuer, jwksURI, timeout)
297297
if err != nil {
298-
log.Infof("The JWKS key is not yet fetched for issuer %s (%s), using a fake JWKS for now", jwtIssuer, jwksURI)
299-
// This is a temporary workaround to reject a request with JWT token by using a fake jwks when istiod failed to fetch it.
300-
// TODO(xulingqing): Find a better way to reject the request without using the fake jwks.
301-
jwtPubKey = FakeJwks
298+
log.Warnf("JWKS fetch failed for issuer %s (%s), using public-only JWKS with discarded private key - JWT requests will be rejected", jwtIssuer, jwksURI)
299+
// fail closed: use a public key where the private key has been permanently discarded
300+
// nobody can sign valid JWTs because the private key doesn't exist anywhere
301+
jwtPubKey = PublicOnlyJwks
302302
}
303303
}
304304
return &envoy_jwt.JwtProvider_LocalJwks{
@@ -310,29 +310,17 @@ func (r *JwksResolver) BuildLocalJwks(jwksURI, jwtIssuer, jwtPubKey string, time
310310
}
311311
}
312312

313-
// FakeJwks is a fake jwks, generated by following code
314-
/*
315-
fakeJwksRSAKey, _ := rsa.GenerateKey(rand.Reader, 2048)
316-
key, _ := jwk.FromRaw(fakeJwksRSAKey)
317-
rsaKey, _ := key.(jwk.RSAPrivateKey)
318-
res, _ := json.Marshal(rsaKey)
319-
fmt.Printf("{\"keys\":[ %s]}\n", string(res))
320-
*/
321-
// it should be static across different instances and versions.
322-
// more details can be found: https://github.com/istio/istio/pull/47661.
313+
// PublicOnlyJwks is a JWKS containing a public RSA key where the private key was generated and immediately discarded.
314+
// This is used as a fail-closed fallback when JWKS fetch fails - since nobody has the private key, no valid JWTs can be forged.
315+
// The private key (d, p, q, dp, dq, qi) does not exist anywhere, making it cryptographically impossible to sign JWTs that
316+
// would validate against this public key.
323317
// nolint: lll
324-
const FakeJwks = `{
318+
const PublicOnlyJwks = `{
325319
"keys": [
326320
{
327-
"d": "T6cYL1_1mWHQLtOcbOgWV6HjhS0HVh3Apt4xEar5beaMBX3IYLFITz684DOHNy5dzaxTRqvGj-zHEgNrgy2T-Izoo2Z-xJ2Zse6wQ4R0xbwd0by8IbhiePcjgNWXXzildMHkBVrxNZhUICpb_r8efTHZfEwc6FPjJDVgJKtEc6WGCOiWnRYcGTTlsB5-QrQQlDFLmrU2Z6QDmqJU33aDJFr_qzmRiVNXeHuhlNca2JnKNPpxjRVsy7Kbc8PorxiPijnLzV8_pccsMyLvA8pWUl5FRtAJNSss7x_81HEcInlj7yA896zMiELSPps1rW68yVvpuKEuYulzGi4z74gz0Q",
328-
"dp": "YkH_MFMlgnGZntOCXLhib1LLW1JJCYmTzebn-JSluFJbG_qQgzuZkUu5s2cYBHmiZkDGmnTDOAYXrOaQSgVIBQMPxMqdUf8WjRIlEb88zvKpM_Curp59wuy6MhI7Ej3xKiixHX3bIq5Qujk3ZdsDbHUi3HH56-V7cdFKccqlg6E",
329-
"dq": "CXCwRpRgbtqzLcsfuy-5IUZosrvEDHCrFh0C-A6OYvKpHzn8PDwb62YGddhiHzSrgr1EUgykQxiIF2xG8dBaq8xXg9Bh4G1kkgIsqJmL5DG1lwyh_-Jt4nPyiLHZ--ERc48cjj515uRpGd-CWXdIf2EWYaJNsEkiNaYEClJQIA8",
330-
"e": "AQAB",
331321
"kty": "RSA",
332-
"n": "vqS7RN4b34i3_5YyhygtBe33gI6GK_0ldW8WMZaunS28T-WAzJOAoZ7E9Y0mHS8vcDES0eZIUpp6Ft9sRPhOlzQfo_7l-3DnaD9LxJVKdXjE1jugxfI9YX1qJpD9S9wRZxQIhPky9UzZDkpFh_KpL6pZUt4cbPtW0VCctjqvpI11yHNk4CEbzw-RRFLMJkLFJqgPa2JPzGZ-TqJdkSDQ7UtRiKzjRcWGnAdLsTq6WabDA1Fn1JVI9TWu-YDbLufDUDco46qyPgpxAqcRQG39cWZAQzMwNEZ-Yec_WiqDYqGTU6K8BBWeEIuMhiWfxGmtqX35rb9Qk_qeYDsqqT95Pw",
333-
"p": "7EK8xaN7qCdWCeQ1ptXWvuc6qotZc6oD-j1ecgel9FqmfkmaioVEbEAfP_N73QAjw-sU60sK3XK8LV4fkGUoJV-MDvmiCzy3wUPe-adSaTCxFykgOm6SPA9NKCqAh8lUm6GUm9RZkjwkv4xzZ8pJjng3d74WXx7zhTEH6yi4E00",
334-
"q": "zpJPbhAn79s_jPm4OhOvvPKT-ISN6EyLu_g6joh1Dzf-HCF149KKQfuLDtwDCsCNf1cE_BCb4qoHAVBLDjbqusQF019zNIFTHeUL8oMpbv-5of7km0K8oo-DQp5b8u05PKaEQu3OXmRZFwuO6dSTPvXO094X-8vm791FLcJ-4Ls",
335-
"qi": "SXz-JeBcTYMcO5lDBlrI9qd2eMQAYfVFDyq523L-RFhdravaxaYutT7dWk5f4Smzbh5KtvKifcFUMnV88On4HCiTrdBjLJJhIYqZQwzP8hYbXZlw4SvCtXKUrvLwLEUQaYg6bopp4VJ5c3XCZD5z3paHlZ45oCDsMeSEWxAD6lo"
322+
"e": "AQAB",
323+
"n": "0xObjM0UvS_oaazjpEYlAbwctEJ4L8pH3OuTb7qth7gUwqet-EzQB4dgFdvSdMgrLnSncQGRjpEYz3F3viIbH-3EN3TxSlPNviHxeOdyiBVfumN8dMxbLvVJUpfNOnvmMxJcl-8NNjAwcOjk4otSALaYgYYyOPyvKtgVdrQr-FoubWX4yrjxW-MJ2-7OBeepUUNOsVwGV23YX03sVkkyvY3otRflkBcY3_HKpBxJl9wk2GyOShN4_PNUF9-vwfnvOXMbCDX-w4PTef4geeb_GiT40YCKHTKMSPVanGRn5GExIWmki-mmqh94-mPTJyBR74ShspL3BxPZitDL574T1w"
336324
}
337325
]
338326
}`

0 commit comments

Comments
 (0)