Skip to content

Commit b0748a0

Browse files
cnvergencezirain
authored andcommitted
validate only sa
Signed-off-by: Karol Szwaj <[email protected]>
1 parent be013d7 commit b0748a0

File tree

3 files changed

+28
-83
lines changed

3 files changed

+28
-83
lines changed

internal/xds/cache/snapshotcache.go

-14
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ type SnapshotCacheWithCallbacks interface {
4747
cachev3.SnapshotCache
4848
serverv3.Callbacks
4949
GenerateNewSnapshot(string, types.XdsResources) error
50-
SnapshotHasIrKey(string) bool
5150
GetIrKeys() []string
5251
}
5352

@@ -366,19 +365,6 @@ func (s *snapshotCache) OnFetchRequest(_ context.Context, _ *discoveryv3.Discove
366365
func (s *snapshotCache) OnFetchResponse(_ *discoveryv3.DiscoveryRequest, _ *discoveryv3.DiscoveryResponse) {
367366
}
368367

369-
func (s *snapshotCache) SnapshotHasIrKey(irKey string) bool {
370-
s.mu.Lock()
371-
defer s.mu.Unlock()
372-
373-
for key, snapshot := range s.lastSnapshot {
374-
if snapshot != nil && key == irKey {
375-
return true
376-
}
377-
}
378-
379-
return false
380-
}
381-
382368
func (s *snapshotCache) GetIrKeys() []string {
383369
s.mu.Lock()
384370
defer s.mu.Unlock()

internal/xds/server/kubejwt/jwtinterceptor.go

+21-46
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
package kubejwt
77

88
import (
9-
"context"
109
"fmt"
1110
"strings"
1211

13-
discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
1412
"google.golang.org/grpc"
1513
"google.golang.org/grpc/metadata"
1614
"k8s.io/client-go/kubernetes"
@@ -36,56 +34,33 @@ func NewJWTAuthInterceptor(clientset *kubernetes.Clientset, issuer, audience str
3634
}
3735
}
3836

39-
type wrappedStream struct {
40-
grpc.ServerStream
41-
ctx context.Context
42-
interceptor *JWTAuthInterceptor
43-
validated bool
37+
// Stream intercepts streaming gRPC calls for authentication.
38+
func (i *JWTAuthInterceptor) Stream() grpc.StreamServerInterceptor {
39+
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
40+
if err := i.authorize(ss); err != nil {
41+
return err
42+
}
43+
return handler(srv, ss)
44+
}
4445
}
4546

46-
func (w *wrappedStream) RecvMsg(m any) error {
47-
err := w.ServerStream.RecvMsg(m)
48-
if err != nil {
49-
return err
47+
// authorize validates the Kubernetes Service Account JWT token from the metadata.
48+
func (i *JWTAuthInterceptor) authorize(ss grpc.ServerStream) error {
49+
md, ok := metadata.FromIncomingContext(ss.Context())
50+
if !ok {
51+
return fmt.Errorf("missing metadata")
5052
}
5153

52-
if !w.validated {
53-
if req, ok := m.(*discoveryv3.DeltaDiscoveryRequest); ok {
54-
if req.Node == nil || req.Node.Id == "" {
55-
return fmt.Errorf("missing node ID in request")
56-
}
57-
nodeID := req.Node.Id
58-
59-
md, ok := metadata.FromIncomingContext(w.ctx)
60-
if !ok {
61-
return fmt.Errorf("missing metadata")
62-
}
63-
64-
authHeader := md.Get("authorization")
65-
if len(authHeader) == 0 {
66-
return fmt.Errorf("missing authorization token in metadata: %s", md)
67-
}
68-
token := strings.TrimPrefix(authHeader[0], "Bearer ")
69-
70-
if err := w.interceptor.validateKubeJWT(w.ctx, token, nodeID); err != nil {
71-
return fmt.Errorf("failed to validate token: %w", err)
72-
}
54+
authHeader, exists := md["authorization"]
55+
if !exists || len(authHeader) == 0 {
56+
return fmt.Errorf("missing authorization token in metadata: %s", md)
57+
}
58+
tokenStr := strings.TrimPrefix(authHeader[0], "Bearer ")
7359

74-
w.validated = true
75-
}
60+
err := i.validateKubeJWT(ss.Context(), tokenStr)
61+
if err != nil {
62+
return fmt.Errorf("failed to validate token: %w", err)
7663
}
7764

7865
return nil
7966
}
80-
81-
func newWrappedStream(s grpc.ServerStream, ctx context.Context, interceptor *JWTAuthInterceptor) grpc.ServerStream {
82-
return &wrappedStream{s, ctx, interceptor, false}
83-
}
84-
85-
// Stream intercepts streaming gRPC calls for authorization.
86-
func (i *JWTAuthInterceptor) Stream() grpc.StreamServerInterceptor {
87-
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
88-
wrapped := newWrappedStream(ss, ss.Context(), i)
89-
return handler(srv, wrapped)
90-
}
91-
}

internal/xds/server/kubejwt/tokenreview.go

+7-23
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import (
1111
"slices"
1212
"strings"
1313

14-
"github.com/envoyproxy/gateway/internal/envoygateway/config"
15-
"github.com/envoyproxy/gateway/internal/utils"
1614
authenticationv1 "k8s.io/api/authentication/v1"
1715
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18-
"k8s.io/apiserver/pkg/authentication/serviceaccount"
1916
"k8s.io/client-go/kubernetes"
2017
"k8s.io/client-go/rest"
18+
19+
"github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/proxy"
2120
)
2221

2322
// GetKubernetesClient creates a Kubernetes client using in-cluster configuration.
@@ -35,7 +34,7 @@ func GetKubernetesClient() (*kubernetes.Clientset, error) {
3534
return clientset, nil
3635
}
3736

38-
func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token, nodeID string) error {
37+
func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token string) error {
3938
tokenReview := &authenticationv1.TokenReview{
4039
Spec: authenticationv1.TokenReviewSpec{
4140
Token: token,
@@ -56,36 +55,21 @@ func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token, nodeID
5655
return fmt.Errorf("token is not authenticated")
5756
}
5857

59-
if tokenReview.Status.User.Extra != nil {
60-
podName := tokenReview.Status.User.Extra[serviceaccount.PodNameKey]
61-
if podName[0] == "" {
62-
return fmt.Errorf("pod name not found in token review response")
63-
}
64-
65-
if podName[0] != nodeID {
66-
return fmt.Errorf("pod name mismatch: expected %s, got %s", nodeID, podName[0])
67-
}
68-
}
69-
7058
// Check if the service account name in the JWT token exists in the cache to verify that the token
7159
// is valid for an Envoy proxy managed by Envoy Gateway.
7260
// example: "system:serviceaccount:default:envoy-default-eg-e41e7b31"
73-
parts:=strings.Split(tokenReview.Status.User.Username, ":")
61+
parts := strings.Split(tokenReview.Status.User.Username, ":")
7462
if len(parts) != 4 {
7563
return fmt.Errorf("invalid username format: %s", tokenReview.Status.User.Username)
7664
}
7765
sa := parts[3]
7866

79-
irKeys:=i.cache.GetIrKeys()
67+
irKeys := i.cache.GetIrKeys()
8068
for _, irKey := range irKeys {
81-
if irKey2ServiceAccountName(irKey) == sa {
69+
if proxy.ExpectedResourceHashedName(irKey) == sa {
8270
return nil
8371
}
8472
}
85-
return fmt.Errorf("Envoy service account %s not found in the cache", sa)
86-
}
8773

88-
func irKey2ServiceAccountName(irKey string) string {
89-
hashedName := utils.GetHashedName(irKey, 48)
90-
return fmt.Sprintf("%s-%s", config.EnvoyPrefix, hashedName)
74+
return fmt.Errorf("envoy service account %s not found in the cache", sa)
9175
}

0 commit comments

Comments
 (0)