Skip to content

Commit ede977e

Browse files
committed
Refactor grpc authorizer
Signed-off-by: Jian Qiu <jqiu@redhat.com>
1 parent 18b20e1 commit ede977e

5 files changed

Lines changed: 130 additions & 91 deletions

File tree

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package authz
22

33
import (
44
"context"
5-
6-
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
5+
"google.golang.org/grpc"
76
)
87

9-
type Authorizer interface {
10-
Authorize(ctx context.Context, cluster string, eventsType types.CloudEventsType) error
8+
type UnaryAuthorizer interface {
9+
AuthorizeRequest(ctx context.Context, req any) error
10+
}
11+
12+
type StreamAuthorizer interface {
13+
AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (grpc.ServerStream, error)
1114
}

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

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

711
authv1 "k8s.io/api/authorization/v1"
812
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -17,20 +21,95 @@ import (
1721
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1822
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
1923
"open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authn"
20-
"open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authz"
2124
)
2225

2326
type SARAuthorizer struct {
2427
kubeClient kubernetes.Interface
2528
}
2629

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

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func TestSARAuthorize(t *testing.T) {
197197

198198
auth := NewSARAuthorizer(client)
199199

200-
err := auth.Authorize(tc.userCtx(), tc.cluster, tc.eventsType)
200+
err := auth.authorize(tc.userCtx(), tc.cluster, tc.eventsType)
201201
if tc.expectErr && err == nil {
202202
t.Errorf("expected error, got nil")
203203
}

pkg/cloudevents/server/grpc/options/server.go

Lines changed: 37 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,12 @@ import (
55
"crypto/tls"
66
"crypto/x509"
77
"fmt"
8-
"os"
9-
"sync"
10-
118
"google.golang.org/grpc"
129
"google.golang.org/grpc/credentials"
1310
"google.golang.org/grpc/keepalive"
11+
"k8s.io/apimachinery/pkg/util/errors"
12+
"os"
1413

15-
"k8s.io/klog/v2"
16-
17-
pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1"
1814
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
1915
"open-cluster-management.io/sdk-go/pkg/cloudevents/server"
2016
grpcserver "open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc"
@@ -29,11 +25,12 @@ type PreStartHook interface {
2925
}
3026

3127
type Server struct {
32-
options *GRPCServerOptions
33-
authenticators []authn.Authenticator
34-
authorizers []authz.Authorizer
35-
services map[types.CloudEventsDataType]server.Service
36-
hooks []PreStartHook
28+
options *GRPCServerOptions
29+
authenticators []authn.Authenticator
30+
unarayAuthorizers []authz.UnaryAuthorizer
31+
streamAuthorizers []authz.StreamAuthorizer
32+
services map[types.CloudEventsDataType]server.Service
33+
hooks []PreStartHook
3734
}
3835

3936
func NewServer(opt *GRPCServerOptions) *Server {
@@ -45,8 +42,13 @@ func (s *Server) WithAuthenticator(authenticator authn.Authenticator) *Server {
4542
return s
4643
}
4744

48-
func (s *Server) WithAuthorizer(authorizer authz.Authorizer) *Server {
49-
s.authorizers = append(s.authorizers, authorizer)
45+
func (s *Server) WithUnarayAuthorizer(authorizer authz.UnaryAuthorizer) *Server {
46+
s.unarayAuthorizers = append(s.unarayAuthorizers, authorizer)
47+
return s
48+
}
49+
50+
func (s *Server) WithStreamAuthorizer(authorizer authz.StreamAuthorizer) *Server {
51+
s.streamAuthorizers = append(s.streamAuthorizers, authorizer)
5052
return s
5153
}
5254

@@ -110,10 +112,10 @@ func (s *Server) Run(ctx context.Context) error {
110112
grpcServerOptions = append(grpcServerOptions,
111113
grpc.ChainUnaryInterceptor(
112114
newAuthnUnaryInterceptor(s.authenticators...),
113-
newAuthzUnaryInterceptor(s.authorizers...)),
115+
newAuthzUnaryInterceptor(s.unarayAuthorizers...)),
114116
grpc.ChainStreamInterceptor(
115117
newAuthnStreamInterceptor(s.authenticators...),
116-
newAuthzStreamInterceptor(s.authorizers...)))
118+
newAuthzStreamInterceptor(s.streamAuthorizers...)))
117119

118120
grpcServer := grpc.NewServer(grpcServerOptions...)
119121
grpcEventServer := grpcserver.NewGRPCBroker(grpcServer)
@@ -155,37 +157,27 @@ func newAuthnUnaryInterceptor(authenticators ...authn.Authenticator) grpc.UnaryS
155157
}
156158
}
157159

158-
func newAuthzUnaryInterceptor(authorizers ...authz.Authorizer) grpc.UnaryServerInterceptor {
160+
func newAuthzUnaryInterceptor(authorizers ...authz.UnaryAuthorizer) grpc.UnaryServerInterceptor {
159161
return func(
160162
ctx context.Context,
161163
req interface{},
162164
info *grpc.UnaryServerInfo,
163165
handler grpc.UnaryHandler,
164166
) (interface{}, error) {
167+
var errs []error
165168
for _, authorizer := range authorizers {
166-
pReq, ok := req.(*pbv1.PublishRequest)
167-
if !ok {
168-
return nil, fmt.Errorf("unsupported request type %T", req)
169-
}
170-
171-
eventsType, err := types.ParseCloudEventsType(pReq.Event.Type)
172-
if err != nil {
173-
return nil, err
174-
}
175-
176-
// the event of grpc publish request is the original cloudevent data, we need a `ce-` prefix
177-
// to get the event attribute
178-
clusterAttr, ok := pReq.Event.Attributes[fmt.Sprintf("ce-%s", types.ExtensionClusterName)]
179-
if !ok {
180-
return nil, fmt.Errorf("missing ce-clustername in event attributes, %v", pReq.Event.Attributes)
169+
if err := authorizer.AuthorizeRequest(ctx, req); err == nil {
170+
return handler(ctx, req)
171+
} else {
172+
errs = append(errs, err)
181173
}
174+
}
182175

183-
if err := authorizer.Authorize(ctx, clusterAttr.GetCeString(), *eventsType); err != nil {
184-
return nil, err
185-
}
176+
if len(errs) > 0 {
177+
return nil, errors.NewAggregate(errs)
186178
}
187179

188-
return handler(ctx, req)
180+
return nil, fmt.Errorf("no authorizer found for %s", info.FullMethod)
189181
}
190182
}
191183

@@ -236,32 +228,8 @@ func newAuthnStreamInterceptor(authenticators ...authn.Authenticator) grpc.Strea
236228
}
237229
}
238230

239-
// wrappedAuthorizedStream caches the subscription request that is already read.
240-
type wrappedAuthorizedStream struct {
241-
sync.Mutex
242-
243-
grpc.ServerStream
244-
authorizedReq *pbv1.SubscriptionRequest
245-
}
246-
247-
// RecvMsg set the msg from the cache.
248-
func (c *wrappedAuthorizedStream) RecvMsg(m any) error {
249-
c.Lock()
250-
defer c.Unlock()
251-
252-
msg, ok := m.(*pbv1.SubscriptionRequest)
253-
if !ok {
254-
return fmt.Errorf("unsupported request type %T", m)
255-
}
256-
257-
msg.ClusterName = c.authorizedReq.ClusterName
258-
msg.Source = c.authorizedReq.Source
259-
msg.DataType = c.authorizedReq.DataType
260-
return nil
261-
}
262-
263-
// newAuthzStreamInterceptor is a stream interceptor that authorizes the subscription request.
264-
func newAuthzStreamInterceptor(authorizers ...authz.Authorizer) grpc.StreamServerInterceptor {
231+
// newAuthzStreamInterceptor is a stream interceptor that authorizes the stream request.
232+
func newAuthzStreamInterceptor(authorizers ...authz.StreamAuthorizer) grpc.StreamServerInterceptor {
265233
return func(
266234
srv interface{},
267235
ss grpc.ServerStream,
@@ -272,32 +240,19 @@ func newAuthzStreamInterceptor(authorizers ...authz.Authorizer) grpc.StreamServe
272240
return handler(srv, ss)
273241
}
274242

275-
var req pbv1.SubscriptionRequest
276-
if err := ss.RecvMsg(&req); err != nil {
277-
return err
278-
}
279-
280-
eventDataType, err := types.ParseCloudEventsDataType(req.DataType)
281-
if err != nil {
282-
return err
283-
}
284-
285-
eventsType := types.CloudEventsType{
286-
CloudEventsDataType: *eventDataType,
287-
SubResource: types.SubResourceSpec,
288-
Action: types.WatchRequestAction,
289-
}
243+
var errs []error
290244
for _, authorizer := range authorizers {
291-
if err := authorizer.Authorize(ss.Context(), req.ClusterName, eventsType); err != nil {
292-
return err
245+
if authorizedStream, err := authorizer.AuthorizeStream(ss.Context(), ss, info); err == nil {
246+
return handler(srv, authorizedStream)
247+
} else {
248+
errs = append(errs, err)
293249
}
294250
}
295251

296-
if err := handler(srv, &wrappedAuthorizedStream{ServerStream: ss, authorizedReq: &req}); err != nil {
297-
klog.Error(err)
298-
return err
252+
if len(errs) > 0 {
253+
return errors.NewAggregate(errs)
299254
}
300255

301-
return nil
256+
return fmt.Errorf("no authorizer found for %s", info.FullMethod)
302257
}
303258
}

test/integration/cloudevents/broker/grpc.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import (
1010
)
1111

1212
func NewGRPCBrokerServer(opt *options.GRPCServerOptions, svc *services.ResourceService) *options.Server {
13+
authorizer := sar.NewSARAuthorizer(util.KubeAuthzClient())
1314
return options.NewServer(opt).
1415
WithService(payload.ManifestBundleEventDataType, svc).
1516
WithAuthenticator(grpcauthn.NewTokenAuthenticator(util.KubeAuthnClient())).
1617
WithAuthenticator(grpcauthn.NewMtlsAuthenticator()).
17-
WithAuthorizer(sar.NewSARAuthorizer(util.KubeAuthzClient()))
18+
WithUnarayAuthorizer(authorizer).
19+
WithStreamAuthorizer(authorizer)
1820
}

0 commit comments

Comments
 (0)