Skip to content

Commit daca64c

Browse files
committed
Refactor grpc pkg to support general GRPC service.
Signed-off-by: xuezhaojun <zxue@redhat.com>
1 parent 18b20e1 commit daca64c

21 files changed

Lines changed: 711 additions & 457 deletions

File tree

pkg/cloudevents/server/grpc/authn/interface.go

Lines changed: 0 additions & 22 deletions
This file was deleted.

pkg/cloudevents/server/grpc/authz/interface.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

pkg/cloudevents/server/grpc/authz/kube/sar.go

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package sar
33
import (
44
"context"
55
"fmt"
6+
"sync"
7+
8+
"google.golang.org/grpc"
9+
pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1"
10+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authz"
611

712
authv1 "k8s.io/api/authorization/v1"
813
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -16,41 +21,123 @@ import (
1621
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/lease"
1722
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1823
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
19-
"open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authn"
20-
"open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authz"
24+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authn"
2125
)
2226

2327
type SARAuthorizer struct {
2428
kubeClient kubernetes.Interface
2529
}
2630

27-
func NewSARAuthorizer(kubeClient kubernetes.Interface) authz.Authorizer {
31+
// validate SARAuthorizer implement StreamAuthorizer and UnaryAuthorizer
32+
var _ authz.StreamAuthorizer = (*SARAuthorizer)(nil)
33+
var _ authz.UnaryAuthorizer = (*SARAuthorizer)(nil)
34+
35+
// wrappedAuthorizedStream caches the subscription request that is already read.
36+
type wrappedAuthorizedStream struct {
37+
sync.Mutex
38+
39+
grpc.ServerStream
40+
authorizedReq *pbv1.SubscriptionRequest
41+
}
42+
43+
// RecvMsg set the msg from the cache.
44+
func (c *wrappedAuthorizedStream) RecvMsg(m any) error {
45+
c.Lock()
46+
defer c.Unlock()
47+
48+
msg, ok := m.(*pbv1.SubscriptionRequest)
49+
if !ok {
50+
return fmt.Errorf("unsupported request type %T", m)
51+
}
52+
53+
msg.ClusterName = c.authorizedReq.ClusterName
54+
msg.Source = c.authorizedReq.Source
55+
msg.DataType = c.authorizedReq.DataType
56+
return nil
57+
}
58+
59+
func NewSARAuthorizer(kubeClient kubernetes.Interface) *SARAuthorizer {
2860
return &SARAuthorizer{
2961
kubeClient: kubeClient,
3062
}
3163
}
3264

33-
func (s *SARAuthorizer) Authorize(ctx context.Context, cluster string, eventsType types.CloudEventsType) error {
65+
func (s *SARAuthorizer) AuthorizeRequest(ctx context.Context, req any) (authz.Decision, error) {
66+
pReq, ok := req.(*pbv1.PublishRequest)
67+
if !ok {
68+
return authz.DecisionDeny, fmt.Errorf("unsupported request type %T", req)
69+
}
70+
71+
eventsType, err := types.ParseCloudEventsType(pReq.Event.Type)
72+
if err != nil {
73+
return authz.DecisionDeny, err
74+
}
75+
76+
// the event of grpc publish request is the original cloudevent data, we need a `ce-` prefix
77+
// to get the event attribute
78+
clusterAttr, ok := pReq.Event.Attributes[fmt.Sprintf("ce-%s", types.ExtensionClusterName)]
79+
if !ok {
80+
return authz.DecisionDeny, fmt.Errorf("missing ce-clustername in event attributes, %v", pReq.Event.Attributes)
81+
}
82+
83+
decision, err := s.authorize(ctx, clusterAttr.GetCeString(), *eventsType)
84+
return decision, err
85+
}
86+
87+
func (s *SARAuthorizer) AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (authz.Decision, grpc.ServerStream, error) {
88+
if info.IsClientStream {
89+
return authz.DecisionAllow, ss, nil
90+
}
91+
92+
if info.FullMethod != pbv1.CloudEventService_Subscribe_FullMethodName {
93+
return authz.DecisionDeny, nil, fmt.Errorf("unsupported service full method %s for SARAuthorizer", info.FullMethod)
94+
}
95+
96+
var req pbv1.SubscriptionRequest
97+
if err := ss.RecvMsg(&req); err != nil {
98+
return authz.DecisionDeny, nil, err
99+
}
100+
101+
eventDataType, err := types.ParseCloudEventsDataType(req.DataType)
102+
if err != nil {
103+
return authz.DecisionDeny, nil, err
104+
}
105+
106+
eventsType := types.CloudEventsType{
107+
CloudEventsDataType: *eventDataType,
108+
SubResource: types.SubResourceSpec,
109+
Action: types.WatchRequestAction,
110+
}
111+
112+
decision, err := s.authorize(ss.Context(), req.ClusterName, eventsType)
113+
if err != nil {
114+
return decision, nil, err
115+
}
116+
117+
return decision, &wrappedAuthorizedStream{ServerStream: ss, authorizedReq: &req}, nil
118+
}
119+
120+
func (s *SARAuthorizer) authorize(ctx context.Context, cluster string, eventsType types.CloudEventsType) (authz.Decision, error) {
34121
user, groups, err := userInfo(ctx)
35122
if err != nil {
36-
return err
123+
return authz.DecisionDeny, err
37124
}
38125

39126
sar, err := toSubjectAccessReview(cluster, user, groups, eventsType)
40127
if err != nil {
41-
return err
128+
return authz.DecisionDeny, err
42129
}
43130

44131
created, err := s.kubeClient.AuthorizationV1().SubjectAccessReviews().Create(
45132
ctx, sar, metav1.CreateOptions{})
46133
if err != nil {
47-
return err
134+
return authz.DecisionDeny, err
48135
}
49136
if !created.Status.Allowed {
50-
return fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
137+
return authz.DecisionDeny, fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
51138
eventsType, cluster, sar.Spec, created.Status)
52139
}
53-
return nil
140+
return authz.DecisionAllow, nil
54141
}
55142

56143
func userInfo(ctx context.Context) (user string, groups []string, err error) {

pkg/cloudevents/server/grpc/authz/kube/sar_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import (
1717
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/lease"
1818
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1919
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
20-
"open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authn"
20+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authn"
21+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authz"
2122
)
2223

2324
func TestSARAuthorize(t *testing.T) {
@@ -197,13 +198,19 @@ func TestSARAuthorize(t *testing.T) {
197198

198199
auth := NewSARAuthorizer(client)
199200

200-
err := auth.Authorize(tc.userCtx(), tc.cluster, tc.eventsType)
201+
decision, err := auth.authorize(tc.userCtx(), tc.cluster, tc.eventsType)
201202
if tc.expectErr && err == nil {
202203
t.Errorf("expected error, got nil")
203204
}
204205
if !tc.expectErr && err != nil {
205206
t.Errorf("unexpected error: %v", err)
206207
}
208+
if !tc.expectErr && decision != authz.DecisionAllow {
209+
t.Errorf("expected DecisionAllow, got %v", decision)
210+
}
211+
if tc.expectErr && decision != authz.DecisionDeny {
212+
t.Errorf("expected DecisionDeny, got %v", decision)
213+
}
207214
})
208215
}
209216
}
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,15 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"net"
87
"sync"
98
"time"
109

1110
"k8s.io/apimachinery/pkg/api/errors"
12-
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1311

1412
cloudevents "github.com/cloudevents/sdk-go/v2"
1513
"github.com/cloudevents/sdk-go/v2/binding"
1614
cloudeventstypes "github.com/cloudevents/sdk-go/v2/types"
1715
"github.com/google/uuid"
18-
"google.golang.org/grpc"
1916
"google.golang.org/grpc/codes"
2017
"google.golang.org/grpc/status"
2118
"google.golang.org/protobuf/types/known/emptypb"
@@ -44,20 +41,17 @@ var _ server.AgentEventServer = &GRPCBroker{}
4441
// It broadcasts resource spec to agents and listens for resource status updates from them.
4542
type GRPCBroker struct {
4643
pbv1.UnimplementedCloudEventServiceServer
47-
grpcServer *grpc.Server
4844
services map[types.CloudEventsDataType]server.Service
4945
subscribers map[string]*subscriber // registered subscribers
5046
mu sync.RWMutex
5147
}
5248

5349
// NewGRPCBroker creates a new gRPC broker with the given gRPC server.
54-
func NewGRPCBroker(srv *grpc.Server) server.AgentEventServer {
50+
func NewGRPCBroker() *GRPCBroker {
5551
broker := &GRPCBroker{
56-
grpcServer: srv,
5752
subscribers: make(map[string]*subscriber),
5853
services: make(map[types.CloudEventsDataType]server.Service),
5954
}
60-
pbv1.RegisterCloudEventServiceServer(broker.grpcServer, broker)
6155
return broker
6256
}
6357

@@ -78,26 +72,6 @@ func (bkr *GRPCBroker) Subscribers() sets.Set[string] {
7872
return subscribers
7973
}
8074

81-
// Start starts the gRPC broker at the given address
82-
func (bkr *GRPCBroker) Start(ctx context.Context, addr string) {
83-
logger := klog.FromContext(ctx)
84-
logger.Info("Starting gRPC broker at addr", "addr", addr)
85-
lis, err := net.Listen("tcp", addr)
86-
if err != nil {
87-
utilruntime.Must(fmt.Errorf("failed to listen: %v", err))
88-
}
89-
go func() {
90-
if err := bkr.grpcServer.Serve(lis); err != nil {
91-
utilruntime.Must(fmt.Errorf("failed to serve gRPC broker: %v", err))
92-
}
93-
}()
94-
95-
// wait until context is canceled
96-
<-ctx.Done()
97-
klog.Infof("Shutting down gRPC broker")
98-
bkr.grpcServer.GracefulStop()
99-
}
100-
10175
// Publish in stub implementation for agent publish resource status.
10276
func (bkr *GRPCBroker) Publish(ctx context.Context, pubReq *pbv1.PublishRequest) (*emptypb.Empty, error) {
10377
logger := klog.FromContext(ctx)

pkg/cloudevents/server/grpc/server_test.go renamed to pkg/cloudevents/server/grpc/broker_test.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package grpc
33
import (
44
"context"
55
"errors"
6+
"net"
67
"testing"
78

89
cloudevents "github.com/cloudevents/sdk-go/v2"
910
"google.golang.org/grpc"
1011
grpccli "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc"
12+
pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1"
1113
cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
1214
"open-cluster-management.io/sdk-go/pkg/cloudevents/server"
1315
)
@@ -59,17 +61,34 @@ func (s *testService) create(evt *cloudevents.Event) error {
5961
func TestServer(t *testing.T) {
6062
grpcServerOptions := []grpc.ServerOption{}
6163
grpcServer := grpc.NewServer(grpcServerOptions...)
62-
grpcEventServer := NewGRPCBroker(grpcServer)
64+
defer grpcServer.Stop()
65+
66+
grpcEventServer := NewGRPCBroker()
67+
pbv1.RegisterCloudEventServiceServer(grpcServer, grpcEventServer)
6368

6469
svc := &testService{evts: make(map[string]*cloudevents.Event)}
6570
grpcEventServer.RegisterService(dataType, svc)
6671

6772
ctx, cancel := context.WithCancel(context.Background())
6873
defer cancel()
69-
go grpcEventServer.Start(ctx, ":8888")
74+
75+
lis, err := net.Listen("tcp", "127.0.0.1:0")
76+
if err != nil {
77+
t.Fatalf("failed to listen: %v", err)
78+
}
79+
t.Cleanup(func() {
80+
grpcServer.GracefulStop()
81+
_ = lis.Close()
82+
})
83+
84+
go func() {
85+
if err := grpcServer.Serve(lis); err != nil {
86+
t.Errorf("failed to serve: %v", err)
87+
}
88+
}()
7089

7190
grpcClientOptions := grpccli.NewGRPCOptions()
72-
grpcClientOptions.Dialer = &grpccli.GRPCDialer{URL: "localhost:8888"}
91+
grpcClientOptions.Dialer = &grpccli.GRPCDialer{URL: lis.Addr().String()}
7392
agentOption := grpccli.NewAgentOptions(grpcClientOptions, "cluster1", "agent1")
7493
protocol, err := agentOption.CloudEventsOptions.Protocol(ctx, dataType)
7594
if err != nil {
@@ -90,10 +109,10 @@ func TestServer(t *testing.T) {
90109
WithResourceID("test2").
91110
WithClusterName("cluster1").NewEvent()
92111

93-
recivedEventCh := make(chan cloudevents.Event)
112+
receivedEventCh := make(chan cloudevents.Event)
94113
go func() {
95114
if err := cloudEventsClient.StartReceiver(ctx, func(event cloudevents.Event) {
96-
recivedEventCh <- event
115+
receivedEventCh <- event
97116
}); err != nil {
98117
t.Error(err)
99118
}
@@ -110,8 +129,8 @@ func TestServer(t *testing.T) {
110129
t.Fatal(err)
111130
}
112131

113-
recievedEvent := <-recivedEventCh
114-
if recievedEvent.ID() != evt2.ID() {
132+
receivedEvent := <-receivedEventCh
133+
if receivedEvent.ID() != evt2.ID() {
115134
t.Error("received event is different")
116135
}
117136
}

0 commit comments

Comments
 (0)