Skip to content

Commit 6d49603

Browse files
authored
Refactor grpc pkg to support general GRPC service. (#135)
Signed-off-by: xuezhaojun <zxue@redhat.com>
1 parent 18b20e1 commit 6d49603

21 files changed

Lines changed: 713 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: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ package sar
33
import (
44
"context"
55
"fmt"
6+
"sync"
7+
8+
"google.golang.org/grpc"
9+
"k8s.io/klog/v2"
10+
pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1"
11+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authz"
612

713
authv1 "k8s.io/api/authorization/v1"
814
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -16,41 +22,124 @@ import (
1622
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/lease"
1723
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1824
"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"
25+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authn"
2126
)
2227

2328
type SARAuthorizer struct {
2429
kubeClient kubernetes.Interface
2530
}
2631

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

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

39128
sar, err := toSubjectAccessReview(cluster, user, groups, eventsType)
40129
if err != nil {
41-
return err
130+
return authz.DecisionDeny, err
42131
}
43132

44133
created, err := s.kubeClient.AuthorizationV1().SubjectAccessReviews().Create(
45134
ctx, sar, metav1.CreateOptions{})
46135
if err != nil {
47-
return err
136+
return authz.DecisionDeny, err
48137
}
49138
if !created.Status.Allowed {
50-
return fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
139+
return authz.DecisionDeny, fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
51140
eventsType, cluster, sar.Spec, created.Status)
52141
}
53-
return nil
142+
return authz.DecisionAllow, nil
54143
}
55144

56145
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)